惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Securelist
O
OpenAI News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Threat Research - Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Google Online Security Blog
Google Online Security Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
N
News and Events Feed by Topic
S
Security Affairs
SecWiki News
SecWiki News
Project Zero
Project Zero
L
Lohrmann on Cybersecurity
P
Proofpoint News Feed
P
Palo Alto Networks Blog
L
LINUX DO - 最新话题
H
Hacker News: Front Page
Recent Commits to openclaw:main
Recent Commits to openclaw:main
I
Intezer
Simon Willison's Weblog
Simon Willison's Weblog
W
WeLiveSecurity
T
The Exploit Database - CXSecurity.com
K
Kaspersky official blog
The GitHub Blog
The GitHub Blog
I
InfoQ
云风的 BLOG
云风的 BLOG
雷峰网
雷峰网
B
Blog
IT之家
IT之家
AWS News Blog
AWS News Blog
Jina AI
Jina AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Google DeepMind News
Google DeepMind News
Spread Privacy
Spread Privacy
N
News and Events Feed by Topic
Security Latest
Security Latest
美团技术团队
C
Check Point Blog
WordPress大学
WordPress大学
T
Tenable Blog
S
Security @ Cisco Blogs
Last Week in AI
Last Week in AI
博客园 - 聂微东
月光博客
月光博客
博客园 - 【当耐特】
S
Schneier on Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Secure Thoughts
Schneier on Security
Schneier on Security
C
Cisco Blogs
Cyberwarzone
Cyberwarzone

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Кто решает судьбу вашего проекта? Разбираем заинтересованные стороны. BABOK #1 Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Анатомия «живой» кнопки: 5 слоёв, GPU-анимация и трюки с CSS
VolhaIvanova · 2026-06-13 · via Все публикации подряд на Хабре

Хочу поделиться кейсом, когда разработка типичного tab-switcher превращается в настоящее архитектурное решение.

Когда дизайнер приносит макет с декоративной кнопкой — тисненая текстура, фигурные края, анимированый индикатор — первая мысль «нарежем это на картинки и вставим». Но это не самое хорошее решение если нужно поддерживать гибкость в длине текста. Более правильный путь — это CSS-первый подход.

Demo на CodeSandbox

Итак, задача: два таба, активный элемент плавно скользит между ними. Кнопка выглядит как объект из физического мира — тиснёная кожа, фигурные торцы.

Реализация будет состоять из 5 слоев, давайте разберем каждый.

Слой 1: 9-slice паттерн (у нас три части)

Фон всего tab-switcher делится на 3 части (боковые края и центральная часть). Это упрощенная реализация паттерн 9-slice, который пришел из game development. Его суть в том, что Изображение делится на 9 зон. Это кросс-платформенный стандарт: Android реализует его через .9.png, CSS через border-image, Unity через тип спрайта Sliced. Везде одна и та же идея — углы фиксированы, а края тянутся.

Поскольку мой tab-switcher тянется только по горизонтали, достаточно 3 зон.

В CSS — это три background-image на одном элементе:

.tabs {
    position: relative;         /* контекст для absolute детей */
    background-image:
      url("tabs-frame-l.webp"),  /* левый торец */
      url("tabs-frame-r.webp"),  /* правый торец */
      url("tabs-frame-m.webp");  /* середина */
    background-position:
      left center,
      right center,
      34px center;               /* offset = ширина левого торца */
    background-repeat: no-repeat, no-repeat, no-repeat;
    background-size:
      36px 100%,
      36px 100%,
      calc(100% - 68px) 100%;   /* 100% минус два торца */
}

Файлы торцов — 68px шириной (2× для retina), середина — любая ширина, тянется.

Почему не border-image?

border-image — браузерный 9-slice и необходимо меньше кода. Но:

  • Плохой контроль z-порядка с другими backgrounds

  • Нельзя смешивать с pseudo-element слоями

  • Поведение padding-области непредсказуемо

Multi-background даёт полный контроль, поэтому я выбрала его.

Слой 2: тёмная подложка, которая задает цвет

Внутри tab-switcher нужен цвет фонa, который реализован через pseudo-element. Это отлично подходит поскольку это виртуальный HTML-элемент который браузер создаёт в памяти — в DOM его нет, в разметке не нужно создавать отдельный div, но он рендерится как обычный блок.

.tabs::before {
    content: "";
    position: absolute;
    inset: 5px;
    border-radius: 9999px;
    background: #42302e;
    box-shadow: inset 0 0 3px rgba(0,0,0,0.8);
    z-index: 0;
  }

inset: 5px — отступ со всех сторон, подложка чуть меньше frame. border-radius: 9999px — любое большое число = pill-shape без пересчёта при изменении размера.

Слой 3: текстура через mix-blend-mode

Для создания реалистичной фоновой текстуры завела еще один слой также с помощью pseudo-element.

.tabs::after {
    content: "";
    position: absolute;
    inset: 5px;
    border-radius: 9999px;
    background: url("tabs-texture.webp") 0 0 / 603px 259px;
    mix-blend-mode: overlay;
    z-index: 0;
  }

mix-blend-mode в CSS — это механизм определяющий как будут отображаться слои при наложении. Пиксели элемента математически смешиваются с пикселями под ним. Значения пикселей — от 0 до 1.

В нашем случае: есть серо-бежевая текстура кожи через overlay на тёмно-зеленом фоне — получается эффект тиснения. Без mix-blend-mode текстура просто перекрыла бы всё.

Нужно помнить критическое ограничение Safari: mix-blend-mode ломается если элемент находится внутри контейнера с одновременно position: fixed + transform. Safari создаёт изолированный stacking context — смешивание происходит только внутри него, не с внешними слоями.

Существует два способа фикса:

  1. isolation: isolate — явно создаём stacking context

  2. Детект Safari через @supports как запасной вариант

@supports (hanging-punctuation: first) {
    .tabs::after {
      mix-blend-mode: normal;
      background: rgba(255, 200, 100, 0.08); /*rgba-аппроксимация */
    }
  }

isolation: isolate решает проблему в большинстве случаев. @supports-хак — когда нужен точный rgba-fallback.

Слой 4: Sliding indicator

Этот слой для выделения активного таба. Он реализован ввиде отдельного div с абсолютным позиционированнием, тоже состоит из 3-частей и занимает ровно половину контейнера:

.tab-indicator {
    position: absolute;
    left: 8px;
    top: 8px;
    bottom: 8px;
    width: calc(50% - 8px);  /* половина минус отступ */
    z-index: 1;
  }

При переключении — класс меняется, transform едет:

.tab-indicator {
    transform: translateX(0);
    transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
  }

.tab-indicator--right {
    transform: translateX(100%);
  }

Почему transform, а не left?

Главное правило анимаций в вебе: анимировать только transform и opacity.

left / top / width / height вызывают reflow — браузер пересчитывает геометрию всей страницы на каждом кадре. На 60fps это 60 пересчётов в секунду. На слабых мобильных устройствах — заметное подёргивание.

transform выполняется на composite layer. Браузер выносит анимацию на GPU, не трогая DOM. Плавно даже на бюджетных телефонах.

transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);

cubic-bezier(0.4, 0, 0.2, 1) — Material Design Standard easing. Быстрый разгон в начале, плавное торможение к концу. Ощущается как физичное движение — не роботизированное linear, не «резиновое» ease-in-out.

Слой 5: текст

Кнопки с текстом лежат поверх всего благодаря z-index: 2, position: relative. Цвет синхронно меняется с движением индикатора:

.tab-button {
    color: #fce7b6;  /* золотой — неактивный */
    transition: color 0.35s ease-in-out;
  }

  .tab-button.active {
    color: #221c18;  /* тёмный — активный (читается на светлом indicator) */
  }

Два независимых перехода — transform индикатора и color текста — одинаковая длительность 0.35s. Браузер запускает их параллельно, человек воспринимает как единое движение.

Таким образом, итоговая схема слоёв выглядит так:

  • z-index 2 | .tab-button (текст на кнопках)

  • z-index 1 | .tab-indicator (sliding frame, transform)

  • z-index 0 | .tabs::after (текстура, mix-blend-mode: overlay)

  • z-index 0 | .tabs::before (тёмная подложка)

  • .tabs background-image (3-slice frame)