Создание сложных адаптивных интерфейсов всегда балансирует между эстетикой и функциональностью. Дизайнеры любят оживлять верстку плавающими декоративными элементами: абстрактными геометрическими фигурами, фоновыми паттернами, стрелками и бейджами. Однако при уменьшении ширины экрана или динамическом изменении размера контейнера этот декор начинает наезжать на заголовки и текст, превращая читабельную статью в хаос.
Классическое решение проблемы — использование медиавыражений (@media (max-width: 900px)). Но медиазапросы привязаны к физической ширине вьюпорта браузера, а не к фактическому расстоянию между конкретными блоками на странице. В многоколоночных сетках декор может перекрыть текст даже на широком 4K-мониторе, если соседний сайдбар занял больше места. Долгое время фронтенд-разработчикам приходилось подключать тяжелые скрипты с ResizeObserver или getBoundingClientRect(), создавая паразитные перерасчеты макета (layout thrashing) в основном потоке браузера.
Инженер-дизайнер Ахмад Шадид (Ahmad Shadeed) показал, что современная веб-платформа способна решить эту задачу исключительно средствами чистого CSS, объединив спецификации CSS Anchor Positioning, Scroll-Driven Animations и Container Style Queries.
Механика детекции коллизий: измерение зазора и искусственное переполнение
В основе метода лежит элегантный физический трюк. Чтобы понять, наезжает ли один элемент на другой, браузеру необходимо непрерывно вычислять дистанцию между ними. В этом помогает спецификация CSS Anchor Positioning:
- Декоративному блоку присваивается якорное имя
anchor-name: --decor, а соседнему блоку с текстом —anchor-name: --content; - Между ними создается невидимый служебный элемент
.measure, чьи границы через функциюanchor()жестко привязываются к краям соседей: его левая граница крепится к правому краю декора, а правая — к левому краю контента.
В результате ширина элемента .measure всегда строго равна физическому расстоянию между декором и текстом.
Как превратить изменение ширины в дискретный сигнал для браузера? С помощью переполнения (overflow). Внутрь элемента измерения помещается псевдоэлемент ::after фиксированной ширины — например, 24px (минимально допустимый безопасный зазор). Самому блоку .measure выставляется свойство overflow-x: auto. Пока зазор между декором и текстом больше 24px, переполнения нет. Но как только расстояние сокращается до 23px и меньше, в блоке .measure возникает горизонтальный скролл.
Подъем области видимости через timeline-scope и триггер анимации
По умолчанию шкала прокрутки scroll-timeline видна только дочерним элементам самого скроллера. Чтобы декоративный элемент снаружи мог среагировать на возникновение переполнения, используется директива timeline-scope.
Объявление timeline-scope: --collision-timeline на общем родительском контейнере (например, на body) «поднимает» (hoists) именованную шкалу времени наверх, делая ее доступной для независимых веток дерева DOM. Это логическое продолжение инженерных поисков, в которых исследовалась детекция пересечения элементов на чистом CSS.
Связка animation-timeline: --collision-timeline запускает мгновенную микро-анимацию длительностью в 1 миллисекунду. Эта анимация не сдвигает пиксели — она переключает пользовательскую CSS-переменную --is-colliding: 1. Далее вступает в силу спецификация Container Style Queries: декоративный элемент слушает изменение переменной через @container style(--is-colliding: 1) и плавно исчезает через opacity: 0; pointer-events: none;.
Практический сценарий реализации и безопасный фоллбек
Посмотрим, как выглядит полная декларативная связка в CSS. В первом листинге представлена разметка и стили для измерения зазора и скрытия декора:
/* 1. Поднимаем шкалу времени на общий предок */
body {
--is-colliding: 0;
timeline-scope: --collision-timeline;
animation: toggle-collision 1ms linear;
animation-timeline: --collision-timeline;
}
/* 2. Назначаем имена якорей элементам макета */
.floating-decor {
anchor-name: --decor;
transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.text-section {
anchor-name: --content;
}
/* 3. Служебный блок измерения зазора между правым краем декора и левым краем текста */
.measure-gap {
position: absolute;
top: anchor(--decor top);
bottom: anchor(--decor bottom);
left: anchor(--decor right);
right: anchor(--content left);
overflow-x: auto;
pointer-events: none;
visibility: hidden;
scroll-timeline: --collision-timeline x;
}
/* 4. Порог безопасного расстояния (24px) */
.measure-gap::after {
content: "";
display: block;
width: 24px;
height: 100%;
}
/* 5. Мгновенное переключение флага коллизии при возникновении скролла */
@keyframes toggle-collision {
from, to {
--is-colliding: 1;
}
}
/* 6. Реактивное скрытие декора через Container Style Queries */
.floating-decor {
@container style(--is-colliding: 1) {
opacity: 0;
pointer-events: none;
}
}
Поскольку связка использует передовые спецификации, критически важно предусмотреть безопасный фоллбек для браузеров без полной поддержки Anchor Positioning:
/* Базовое безопасное поведение: скрытие декора по брейкпоинту */
@media (max-width: 992px) {
.floating-decor {
display: none;
}
}
/* Прогрессивное улучшение: возвращаем декор, если браузер умеет считать коллизии */
@supports (timeline-scope: --test) and (anchor-name: --test) {
@media (max-width: 992px) {
.floating-decor {
display: block;
}
}
}
Готовность к продакшену
Сегодня связка полноценно поддерживается в браузерах на базе Chromium версии 125+ (Chrome, Edge, Brave), где реализованы обе спецификации. В Safari (WebKit) и Firefox (Gecko) поддержка Anchor Positioning и timeline-scope активно дорабатывается в предварительных сборках.
Для продуктовых дизайн-систем этот подход задает новый стандарт: компоненты становятся по-настоящему автономными. Вместо жестких глобальных брейкпоинтов и хрупких JavaScript-обработчиков браузер самостоятельно рассчитывает физическое пространство страницы прямо в конвейере рендеринга.
