null

Нововведения в CSS. :has(), @container, scroll-driven анимации и другие фишки для эффективной разработки

Введение

CSS постоянно развивается, и недавно в стандарт добавился целый ряд потрясающих возможностей, которые могут помочь улучшить работу с адаптивными интерфейсами и анимациями, улучшить производительность и упростить разработку. В этой статье мы рассмотрим некоторые из этих нововведений,- как они работают и какие практические применения они открывают.

Вот что мы рассмотрим в статье:

  • :has() — родительский селектор, которого мы ждали десятилетие.
  • @container и container queries — адаптивность на новом уровне.
  • scroll-driven animations — нативные анимации, привязанные к скроллу.
  • interpolate-size — гибкое масштабирование между размерами.
  • anchor-name и anchor-position — абсолютно новый способ размещения элементов по другим элементам.

 

:has() — первый в истории родительский селектор в CSS

Зачем это нужно?

Селектор :has() — это настоящая революция в CSS, которую ждали больше десяти лет. До недавнего времени CSS не умел смотреть “назад” по иерархии DOM. То есть мы могли применять стили к потомкам, но не могли стилизовать родителя на основе содержимого или состояния дочернего элемента. Это ограничивало многие сценарии, особенно при создании динамических интерфейсов.

С приходом :has() всё изменилось. Он позволяет применять стили к элементу, если внутри него есть другой элемент, соответствующий определённому условию. И это — без JS.

Как обходились раньше?

Пример: вы хотите, чтобы <section> с ошибкой в форме выделялась красной рамкой, если внутри есть .input:invalid.

Раньше это можно было сделать только через JavaScript:

const input = document.querySelector('.input');
input.addEventListener('input', () => {
  input.closest('section').classList.toggle('has-error', !input.checkValidity());
});

И только потом через CSS:

section.has-error {
  border: 2px solid red;
}

Это громоздко, требует JS, нарушает принцип разделения ответственности между стилями и логикой.

Как это решается с :has()

section:has(.input:invalid) {
  border: 2px solid red;
}

Всё. Никакого JavaScript. Родитель сам “реагирует” на состояние вложенного элемента.

Примеры применения в реальных проектах:

Пример 1: Карточка товара со скидкой

<article class="product-card">
  <span class="discount">-20%</span>
  <h2>Куртка зимняя</h2>
</article>
.product-card:has(.discount) {
  border: 2px solid gold;
  background-color: #fffbe6;
}

Если у товара есть скидка — мы стилизуем карточку визуально, автоматически.

Пример 2: Кнопка отправки формы, активируется только при заполнении всех полей

<form>
  <input required type="text" class="name-input">
  <button type="submit">Отправить</button>
</form>
form:not(:has(:invalid)) button {
  background-color: green;
  pointer-events: auto;
}

form:has(:invalid) button {
  background-color: gray;
  pointer-events: none;
}

Это позволяет вам отключать кнопку отправки, пока форма невалидна — без единой строчки JS.

Пример 3: Удаление отступов у последнего абзаца, если нет кнопок

<article class="post">
  <p>Текст поста...</p>
  <div class="actions">
    <button>Лайк</button>
  </div>
</article>
.post:not(:has(.actions)) p:last-child {
  margin-bottom: 0;
}

Если в посте нет блока с кнопками действий — убираем лишний отступ.

Поддержка и особенности

Работает в Chrome, Edge, Safari, Opera (начиная с 2023 года).

В Firefox пока включается вручную, но полноценная поддержка ожидается очень скоро.

Немного медленнее, чем обычные селекторы — поэтому не стоит использовать в глубоких вложенных конструкциях на весь сайт.

Советы по применению

  • Отлично подходит для UI-компонентов, валидации форм, интерактивных карточек.
  • Хорошо работает в связке с :is(), :not(), :where().
  • Даёт чистый, декларативный CSS — меньше JS, больше читабельности и поддержки.

 

@container и container queries — адаптивность на новом уровне

Зачем это нужно?

Container queries — это долгожданная функциональность, которая позволяет делать адаптивный дизайн не только для экрана, но и для контейнера, в котором находится элемент. Раньше для адаптивных интерфейсов мы использовали media queries, которые реагируют на размеры окна браузера. Но что делать, если элемент внутри контейнера должен менять свою стилизацию в зависимости от его собственного размера?

С появлением @container мы можем устанавливать условие для изменения стилей в зависимости от размеров родительского контейнера, а не экрана. Это даёт значительные преимущества для создания сложных и гибких интерфейсов — например, в рамках многоуровневых компонентов или сложных карточек.

Как обходились раньше?

До появления контейнерных запросов для создания адаптивных интерфейсов часто использовали «глобальные» media queries:

/* пример для экрана */
.card {
  width: 100%;
}

@media (max-width: 768px) {
  .card {
    width: 50%;
  }
}

Этот подход работает, но у него есть ограничения:

  • Нужно вручную отслеживать размер окна.
  • Не учитывает ситуации, когда сам контейнер меняет свои размеры.

Как это решается с @container и container queries

С помощью @container мы можем создать условия для стилизации элементов в зависимости от размера их контейнера.

Пример 1: Сетка карточек, которая меняет свою структуру в зависимости от размера контейнера

<div class="container">
  <div class="card">Карточка 1</div>
  <div class="card">Карточка 2</div>
  <div class="card">Карточка 3</div>
</div>
.container {
  container-type: inline-size;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

@container (max-width: 600px) {
  .container {
    grid-template-columns: repeat(2, 1fr);
  }
}

@container (max-width: 400px) {
  .container {
    grid-template-columns: 1fr;
  }
}

container-type: inline-size; — указывает, что этот контейнер будет использовать свой размер для определения адаптивности.

@container (max-width: 600px) — задаёт условия для изменения стилей в зависимости от ширины контейнера, а не окна браузера.

Это даёт вам гибкость и контроль над дизайном компонентов, даже если их размеры меняются внутри более сложной структуры.

Пример 2: Адаптивная карточка продукта с динамичным текстом

<div class="product-card">
  <h2 class="product-title">Продукт</h2>
  <p class="product-description">Очень длинное описание...</p>
</div>
.product-card {
  container-type: inline-size;
  max-width: 400px;
}

@container (max-width: 300px) {
  .product-title {
    font-size: 18px;
  }
  .product-description {
    font-size: 14px;
  }
}

@container (max-width: 200px) {
  .product-title {
    font-size: 16px;
  }
  .product-description {
    font-size: 12px;
  }
}

Здесь мы задаём адаптивные размеры шрифтов для карточки товара в зависимости от размера её контейнера. Чем меньше контейнер, тем меньше становится шрифт, чтобы текст выглядел читаемым на малых экранах.

Пример 3: Картинка, адаптирующаяся под размер контейнера

<div class="image-container">
  <img src="image.jpg" alt="Пример изображения">
</div>
.image-container {
  container-type: inline-size;
}

@container (max-width: 500px) {
  .image-container img {
    width: 100%;
  }
}

@container (min-width: 501px) {
  .image-container img {
    width: 80%;
  }
}

Мы регулируем размер изображения в зависимости от размера контейнера, что даёт плавную адаптивность, особенно при создании гибких галерей или презентационных слайдов.

Если вы хотите, чтобы несколько контейнеров на странице имели уникальные имена, и чтобы вы могли написать условие, ссылающееся на конкретный контейнер, вы можете использовать свойство container-name.

Это особенно полезно, когда у вас много вложенных компонентов, и вы хотите писать гибкие стили, реагирующие только на размеры конкретного родителя.

Синтаксис:

.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

@container sidebar (min-width: 400px) {
  .menu {
    flex-direction: row;
  }
}

Здесь:

.sidebar — контейнер

container-type: inline-size; — делает его "реагирующим"

container-name: sidebar; — даёт имя

@container sidebar (min-width: 400px) — применяет стили, если ширина sidebar-контейнера ≥ 400px

Пример 4 : адаптивная боковая панель

<div class="layout">
  <aside class="sidebar">
    <nav class="menu">...</nav>
  </aside>
  <main class="content">...</main>
</div>
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
  width: 100%;
}

@container sidebar (min-width: 600px) {
  .menu {
    display: flex;
    flex-direction: column;
  }
}

 

Что такое container-type в приведенных выше примерах?

Это CSS-свойство, которое делает элемент контейнером, и задаёт, по каким осям (ширина, высота) к нему можно применять @container-запросы.

Значения container-type:

  • none (по умолчанию) - указывает что элемент не контейнер. Контейнерные запросы не будут работать.
  • inline-size - Только по горизонтали. Применяется в большинстве случаев — выполняется адаптация по ширине.
  • size - По обеим осям: ширина и высота. Применяется когда нужно реагировать и на ширину, и на высоту.
  • normal (новое в спецификации) - Автоматически выбирает подходящий тип (экспериментальное). Пока не поддерживается всеми браузерами.

Поддержка и особенности

  • Поддержка container queries добавлена в Chrome, Edge, Safari, а также в более свежие версии Firefox.
  • Для работы с контейнерными запросами необходимо использовать свойство container-type, которое определяет, какие аспекты контейнера могут быть использованы для вычислений.
  • Есть ограничения в старых версиях браузеров, но с каждым обновлением поддержка расширяется.

Советы по применению

  • Хорошо работает для сложных компонентов и микро-компонентов (например, адаптивные карточки, динамичные списки, фильтры).
  • Прекрасно подходит для состояний внутри компонентов (например, при изменении размера контейнера на мобильных устройствах).
  • Для интерфейсов с внутренними элементами, где используется grid или flexbox, контейнерные запросы делают код гораздо более предсказуемым и контролируемым.

 

Scroll-driven анимации — анимации, связанные с прокруткой страницы

Зачем это нужно?

Scroll-driven анимации — это одна из самых захватывающих новинок в CSS, которая позволяет анимации запускаться в зависимости от того, как пользователь прокручивает страницу. Ранее для реализации таких анимаций использовался JavaScript с событием scroll, что требовало больше вычислений и усилий для контроля, особенно на больших страницах.

Теперь же с помощью нововведений в CSS мы можем создавать анимированные эффекты, которые реагируют на движение прокрутки, и это можно делать без JavaScript, что существенно повышает производительность и упрощает код.

Как обходились раньше?

До появления scroll-driven анимаций в CSS для создания анимаций, завязанных на прокрутке, использовались JavaScript и обработчики событий scroll. Это могло приводить к проблемам с производительностью, особенно на длинных страницах с большим количеством анимаций.

Пример с JavaScript:

window.addEventListener('scroll', () => {
  const element = document.querySelector('.anim');
  const scrollPosition = window.scrollY;
  if (scrollPosition > 100) {
    element.classList.add('visible');
  } else {
    element.classList.remove('visible');
  }
});

Этот код следит за прокруткой и добавляет класс для анимации, но это требует постоянного прослушивания события scroll и перерасчёта стилей, что может быть медленным.

Как это решается с CSS

С помощью scroll-driven анимаций в CSS мы можем напрямую привязать анимированные свойства к прокрутке. Это делает анимации более плавными и производительными.

Пример с использованием scroll-snap-type и scroll-timeline:

Пример 1: Простая анимация с прокруткой

<section class="content">
  <div class="scroll-box">
    <div class="anim">Элемент 1</div>
    <div class="anim">Элемент 2</div>
    <div class="anim">Элемент 3</div>
  </div>
</section>
.scroll-box {
  scroll-snap-type: y mandatory;
  overflow-y: scroll;
  max-height: 400px;
}

.anim {
  scroll-snap-align: start;
  opacity: 0;
  transition: opacity 1s ease;
}

.scroll-box:has(.anim:in-viewport) .anim {
  opacity: 1;
}

scroll-snap-type — используется для создания скроллируемых областей, в которых элементы «приклеиваются» к указанным точкам при прокрутке.

scroll-snap-align — указывает, как элемент будет «прилипать» к экрану при прокрутке.

:in-viewport — проверяет, виден ли элемент на экране, и запускает анимацию на основе прокрутки.

Как только элемент появляется на экране, его непрозрачность изменяется, что создаёт эффект появления.

Пример 2: Прокрутка как запуск анимации

<div class="scroll-container">
  <div class="content-block">Блок 1</div>
  <div class="content-block">Блок 2</div>
  <div class="content-block">Блок 3</div>
</div>
.scroll-container {
  overflow-y: scroll;
  max-height: 100vh;
  scroll-snap-type: y mandatory;
}

.content-block {
  scroll-snap-align: start;
  opacity: 0;
  transition: opacity 0.5s ease-in-out;
}

.scroll-container {
  scroll-timeline: scroll-position 0% 100%;
}

.scroll-container:has(.content-block:in-viewport) .content-block {
  opacity: 1;
}

Здесь используется scroll-timeline, который создаёт временную шкалу анимации, завязанную на прокрутке, позволяя плавно анимировать элементы по мере их появления на экране.

Пример 3: Анимация с изменением масштаба элемента

<div class="scroll-container">
  <div class="zoom-block">Увеличиваем меня!</div>
</div>
.scroll-container {
  overflow-y: scroll;
  max-height: 80vh;
  scroll-snap-type: y mandatory;
}

.zoom-block {
  transition: transform 0.3s ease-out;
  transform: scale(1);
}

.scroll-container:has(.zoom-block:in-viewport) .zoom-block {
  transform: scale(1.5);
}

В этом примере элемент увеличивается в размере, как только он входит в область видимости. Мы использовали transition для плавного изменения масштаба при прокрутке.

Поддержка и особенности

  • Поддержка scroll-driven анимаций пока что появляется постепенно и доступна в новых версиях Chrome, Safari и Edge.
  • Для реализации таких анимаций, помимо scroll-snap-type, нужно использовать new CSS selectors вроде :in-viewport или :has(), что расширяет возможности взаимодействия с прокруткой.
  • Производительность сильно выигрывает, так как теперь не нужно рассчитывать координаты с помощью JavaScript.

Советы по применению

  • Используйте scroll-driven анимации для параллакса и эффектов прокрутки. Они идеально подходят для красивых переходов между элементами, а также для создания эффектов "на лету" без задержек.
  • Подходит для интерактивных интерфейсов, где нужно делать прокрутку более живой и динамичной, например, в лендингах или страницах с длинными списками.
  • Для создания прокачанных интерфейсов, можно использовать сочетание этих анимаций с :has() и @container, чтобы управлять не только прокруткой, но и размером и состоянием компонентов.

 

Interpolate-size — динамическое изменение размера элементов

Зачем это нужно?

С развитием CSS появляются все более мощные функции, которые делают работу с адаптивными макетами проще и гибче. Одним из таких нововведений является interpolate-size. Это свойство позволяет динамически изменять размеры элементов, создавая плавные анимации, которые адаптируются к изменениям внешнего контекста, например, изменению размера контейнера или взаимодействию с пользователем.

Анимация размеров (например, width и height) всегда была сложной задачей в CSS, особенно когда речь шла о динамическом содержимом, где размеры задаются не явно, а зависят от auto. В таких случаях браузеры не могли анимировать эти размеры, и разработчики часто прибегали к хитростям и обходным путям. Теперь же с interpolate-size появилась возможность плавно анимировать размеры, даже когда они зависят от контента.

Ранее для изменения размеров использовались методы, такие как transition и transform, но они часто требовали фиксированных значений и не могли гибко реагировать на изменения контекста. С interpolate-size мы можем анимировать размеры элементов, включая такие изменения, как размер контейнера, или даже использовать auto для автоматического подбора размера.

Как обходились раньше?

До появления interpolate-size для создания анимаций изменения размеров использовались методы с фиксированными значениями через transition и transform. Эти подходы не всегда были достаточно гибкими. Например, можно было плавно изменять размеры, но это всегда требовало указания конкретных значений, и не было учтено, что размеры могут зависеть от других факторов, таких как размер родительского контейнера.

Были ситуации, когда анимация размера с использованием auto была невозможна. Это происходило потому, что браузеры не поддерживали интерполяцию между фиксированными значениями и значением auto. Например, если элемент должен был изменять размер в зависимости от контента, а не от заданной ширины или высоты, анимация не работала. Разработчики использовали различные хитрые подходы, такие как использование временных фиксированных значений, что добавляло сложности и неудобства.

Пример с использованием transition:

.element {
  width: 100px;
  transition: width 0.3s ease;
}

.element:hover {
  width: 200px;
}

Этот код работает, но имеет свои ограничения. Он не позволяет учитывать контекст, например, если элемент должен подстраиваться под размер родителя или других элементов на странице.

Как это решается с помощью interpolate-size?

Interpolate-size решает эту проблему, предоставляя возможность плавно анимировать размеры, даже если они зависят от auto. Это означает, что теперь можно анимировать изменение размеров элементов, которые адаптируются под контент или другие динамические факторы, например, изменения в размере контейнера.

С помощью interpolate-size можно создавать более гибкие и динамичные макеты, где размеры могут адаптироваться в зависимости от контекста, при этом анимация остается плавной и не нарушает визуальное восприятие интерфейса.

Вместо того чтобы задавать фиксированные размеры, можно использовать auto и позволить элементу автоматически адаптироваться к своему содержимому или контейнеру.

Пример 1: Анимация с раскрывающимся контентом

Допустим, у нас есть кнопка, которая раскрывает или скрывает блок с текстом. Ранее для такого эффекта нужно было либо использовать max-height с фиксированным значением, либо добавлять дополнительные JavaScript-решения. Теперь, с interpolate-size, мы можем анимировать изменение высоты элемента, который зависит от содержимого:

<button id="toggle">Открыть</button>
<div class="box">
  <p>Контент, который можно раскрыть и скрыть с анимацией.</p>
</div>
.box {
  overflow: hidden;
  height: 0;
  transition: height 0.5s ease;
  interpolate-size: height;
}

.box.open {
  height: auto;
}
document.getElementById("toggle").addEventListener("click", () => {
  document.querySelector(".box").classList.toggle("open");
});

Объяснение:

transition задаёт продолжительность и эффект анимации для изменения высоты.

height: auto теперь можно анимировать благодаря interpolate-size, что позволяет плавно раскрывать или скрывать содержимое.

overflow: hidden нужен, чтобы скрыть "выпрыгивающий" контент, когда высота меняется.

Пример 2: Выпадающее меню с динамическими пунктами

Теперь, вместо того чтобы использовать фиксированные размеры для меню, можно анимировать его высоту, позволяя динамически добавлять или удалять пункты без необходимости предсказывать максимальную высоту. Этот подход устраняет необходимость в JavaScript-хаке или установке max-height, что значительно упрощает работу с выпадающими меню.

<nav>
  <button id="menu-toggle">Меню</button>
  <ul class="menu">
    <li>Главная</li>
    <li>О нас</li>
    <li>Контакты</li>
  </ul>
</nav>
.menu {
  overflow: hidden;
  height: 0;
  transition: height 0.4s ease;
  interpolate-size: height;
}

.menu.open {
  height: auto;
}
document.getElementById("menu-toggle").addEventListener("click", () => {
  document.querySelector(".menu").classList.toggle("open");
});

Как это работает:

Меню анимируется без фиксированной высоты, что позволяет добавлять и удалять пункты без проблем.

Ранее для подобного эффекта приходилось использовать хак с max-height или полностью контролировать высоту через JavaScript. Теперь CSS справляется сам.

Пример 3: Анимация изменения размера карточек

Теперь представьте, что вы хотите анимировать изменение размеров карточек на странице при наведении. С interpolate-size можно динамично изменять размеры этих карточек, даже если они содержат разное количество текста.

<div class="card-container">
  <div class="card">Карточка 1</div>
  <div class="card">Карточка 2</div>
</div>
.card-container {
  display: flex;
  gap: 20px;
}

.card {
  width: 200px;
  height: 300px;
  background-color: lightblue;
  transition: width 0.5s ease, height 0.5s ease;
  interpolate-size: width, height;
}

.card:hover {
  width: 300px;
  height: 400px;
}

Как это работает:

При наведении на карточку, её размеры плавно изменяются, что создаёт эффект масштабирования.

interpolate-size позволяет анимировать изменения ширины и высоты карточек, учитывая их динамический контент.

Поддержка и особенности

Interpolate-size — это новая функция, которая поддерживается в последних версиях браузеров, но она еще не является повсеместно доступной. Важно следить за поддержкой этой функции и убедиться, что она работает на всех целевых устройствах. Кроме того, использование auto в interpolate-size позволяет обеспечить большую гибкость, но также требует тестирования на разных экранах и устройствах.

Советы по применению

  • Используйте interpolate-size для создания плавных анимаций изменения размеров, таких как изменения размеров карточек, кнопок и других UI-компонентов.
  • Подходит для адаптивных интерфейсов, где элементы должны динамически подстраиваться под изменения размеров экрана или контейнера.
  • Используйте auto в сочетании с interpolate-size для более гибкого управления размерами элементов, особенно когда они должны зависеть от их контента или окружающего контекста.
  • Комбинируйте interpolate-size с media queries и flexbox для создания более динамичных и адаптивных макетов, которые изменяются в ответ на изменения в браузере или на устройстве.

 

Anchor-name — улучшение навигации по якорям с CSS

Зачем это нужно?

Работа с якорями в HTML всегда была важной частью веб-разработки, особенно для создания удобной навигации по длинным страницам. Ранее, чтобы создать якорную ссылку, приходилось использовать только элементы <a> с атрибутом href, что не всегда позволяло гибко контролировать поведение навигации, особенно в случае с динамическими или интерактивными интерфейсами. Теперь с появлением anchor-name в CSS, можно более точно настроить поведение переходов и сделать их динамичными и гибкими без лишних усилий.

С помощью anchor-name можно улучшить поведение якорных переходов, задавая более точное позиционирование, а также стиль и анимации при переходе. Это дает разработчикам больше свободы для работы с прокруткой и улучшает взаимодействие с пользователем.

Как обходились раньше?

Ранее для создания якорной навигации использовались стандартные якорные ссылки:

<a href="#section1">Перейти к разделу 1</a>
<div id="section1">Раздел 1</div>

Однако такие переходы по умолчанию не обеспечивали плавного анимационного эффекта, и не всегда было возможно контролировать точное позиционирование элемента на странице. В некоторых случаях приходилось использовать JavaScript для добавления анимации или манипуляции с прокруткой страницы.

Как это решается с помощью anchor-name?

С anchor-name в CSS мы можем задать более точное поведение якорных переходов. Это свойство позволяет контролировать поведение прокрутки, задавая, например, эффект плавного перехода к якорю, а также настройку точного положения якоря внутри видимой области.

Пример 1: Плавная прокрутка к якорю

<a href="#section1">Перейти к разделу 1</a>
<div id="section1">
  <h2>Раздел 1</h2>
  <p>Это первый раздел страницы.</p>
</div>
html {
  scroll-behavior: smooth;
}

Объяснение:

Этот простой пример демонстрирует использование scroll-behavior: smooth, который добавляет плавную анимацию прокрутки при переходе к якорю. С помощью этой техники прокрутка страницы происходит не резко, а плавно, улучшая пользовательский опыт.

Пример 2: Точное позиционирование с anchor-name

<a href="#section1">Перейти к разделу 1</a>
<div id="section1">
  <h2>Раздел 1</h2>
  <p>Это первый раздел страницы.</p>
</div>
:target {
  scroll-margin-top: 20px;
}

Объяснение:

Здесь мы используем :target, который срабатывает на элемент, к которому был выполнен якорный переход. В данном случае свойство scroll-margin-top позволяет задать отступ сверху, чтобы элемент был немного ниже верхней части экрана, а не в самом верхнем краю, как это происходит по умолчанию.

Пример 3: Анимация при переходе к якорю

<a href="#section2">Перейти ко второму разделу</a>
<div id="section2">
  <h2>Раздел 2</h2>
  <p>Текст во втором разделе.</p>
</div>
#section2 {
  transition: opacity 0.5s ease;
  opacity: 0;
}

#section2:target {
  opacity: 1;
}

Объяснение:

В этом примере, при переходе к якорю с #section2, применяется анимация изменения прозрачности элемента. Сначала элемент невидим (opacity: 0), но когда он становится целью (при переходе), его прозрачность плавно меняется на 1. Это создает приятный визуальный эффект при переходе на определенный раздел.

Особенности и поддержка

На данный момент anchor-name и связанные с ним техники (например, scroll-margin-top) поддерживаются в большинстве современных браузеров. Однако важно помнить, что анимации прокрутки и точного позиционирования с якорями могут иметь небольшие отличия в старых версиях браузеров.

Советы по применению

  • Используйте плавные переходы для улучшения восприятия навигации по странице.
  • Применяйте точное позиционирование элементов с :target для создания аккуратных и приятных переходов.
  • Для динамичных страниц с якорями используйте анимации для улучшения взаимодействия пользователя с интерфейсом.

С появлением anchor-name CSS и связанных с ним улучшений, таких как scroll-margin-top и scroll-behavior, работа с якорями стала более гибкой и мощной. Теперь мы можем не только плавно прокручивать страницы, но и точно настраивать поведение и анимации элементов при переходах, что значительно улучшает пользовательский опыт и делает взаимодействие с сайтом более приятным.

 

anchor-position — контроль выравнивания привязанных элементов

anchor-position — это часть Anchor Positioning API, предназначенная для позиционирования элементов относительно других элементов, а не только по координатам родителя, как это было раньше.

Это особенно полезно для создания всплывающих элементов (tooltip'ов, popover'ов, dropdown'ов и т.д.).

Раньше, если вы хотели отобразить всплывающее окно рядом с кнопкой, вам приходилось:

  • Оборачивать кнопку и поповер в один контейнер.
  • Использовать position: absolute или JavaScript, чтобы высчитать точные координаты.
  • Обрабатывать overflow, смещения и прочие головные боли вручную.

Теперь же с anchor-position всё делается на уровне CSS, декларативно, красиво и гибко.

Как это работает?

Идея такая:

Вы помечаете элемент, к которому хотите привязаться, с помощью anchor-name.

А потом указываете у другого элемента position-anchor, и говорите, где именно он должен быть — например, слева, справа, снизу от якоря.

Синтаксис:

/* Элемент-якорь */
.button {
  anchor-name: --anchor-btn;
}

/* Элемент, который нужно привязать */
.tooltip {
  position: absolute;
  position-anchor: --anchor-btn;
  anchor-default: top; /* или left, right, bottom, center */
}

Пример 1: Подсказка, привязанная к кнопке (anchor)

<button anchor-name="tooltip-btn" class="btn">Нажми меня</button>
<div class="tooltip" position-anchor="tooltip-btn">
  Я — подсказка!
</div>
.btn {
  margin-top: 200px;
  margin-left: 100px;
}

.tooltip {
  position: absolute;
  anchor-position: top;
  top: anchor(top);
  left: anchor(left);
  background: #222;
  color: white;
  padding: 8px;
  border-radius: 4px;
}

Объяснение:

anchor-name="tooltip-btn" создаёт именованный якорь.

position-anchor="tooltip-btn" говорит, что .tooltip будет позиционироваться относительно этой кнопки.

anchor-position: top означает, что элемент будет ориентироваться на верхнюю часть якоря.

top: anchor(top) и left: anchor(left) — значения берутся с якоря, а не с родителя.

Такой подход полностью убирает необходимость JS для привязки попапов!

Пример 2: Всплывающее меню 

<button anchor-name="menu-anchor">Меню</button>
<div class="dropdown" position-anchor="menu-anchor">
  <ul>
    <li>Пункт 1</li>
    <li>Пункт 2</li>
  </ul>
</div>
.dropdown {
  position: absolute;
  anchor-position: bottom;
  top: anchor(bottom);
  left: anchor(left);
  background: white;
  border: 1px solid #ccc;
  padding: 8px;
  width: 150px;
}

Объяснение:

Здесь меню появляется под кнопкой — благодаря anchor-position: bottom.

Если использовать @position-fallback, можно задать альтернативную позицию (например, над кнопкой, если снизу нет места).

Пример 3: Использование anchor-position с fallback’ами

.dropdown {
  position-anchor: menu-anchor;
  anchor-position: bottom;

  @position-fallback {
  @try {
    top: anchor(top);
    anchor-position: top;
    }
  }
}

Как это работает:

Если anchor-position: bottom не срабатывает (например, элемент не помещается вниз), тогда срабатывает fallback: anchor-position: top.

Пример 4: тултип с fallback-позицией

<div class="wrapper">
  <button id="target" anchor-name="my-anchor">Наведи на меня</button>
  <div class="tooltip">Я тултип</div>
</div>
.wrapper {
  position: relative;
  padding: 100px;
}

.tooltip {
  position: absolute;
  anchor-default: my-anchor;
  position-anchor: my-anchor;

  anchor-position: top;
  top: anchor(top);
  left: anchor(center);

  transform: translateX(-50%);
  background: #333;
  color: white;
  padding: 8px 12px;
  border-radius: 4px;

  @position-fallback {
    @try {
      anchor-position: bottom;
      top: anchor(bottom);
    }
    @try {
      anchor-position: right;
      left: anchor(right);
      transform: translateY(-50%);
    }
  }
}

Что делает этот код:

anchor-name="my-anchor" — назначает якорь на элемент <button>.

anchor-default / position-anchor — указывает, что тултип будет позиционироваться относительно этого якоря.

anchor-position: top — по умолчанию тултип должен появляться сверху.

@position-fallback — описывает альтернативные позиции:

Если сверху не влезает → появится снизу

Если и снизу не влезает → появится справа

Преимущества anchor-position

  • Раньше приходилось писать JS для позиционирования, сейчас - только CSS
  • Раньше были проблемы с адаптацией под разные экраны, сейчас - адаптивное поведение из коробки
  • Раньше были трудности с overflow и скроллом, сейчас - автоопределение fallback позиций

Где пригодится на практике?

  • Тултипы - Позиционирование относительно элементов
  • Модальные окна - Привязка к кнопке/иконке
  • Выпадающие меню - Появление под или над элементом
  • Чекбоксы, подсказки - Гибкая адаптация интерфейса на разных экранах

Важно:

Пока свойство находится в стадии внедрения, вам может понадобиться включить флаги в браузере (chrome://flags/#enable-anchor-positioning).

Но скоро оно войдёт в CSS Baseline и станет общедоступным.

Свойство anchor-position в CSS — это большой шаг к тому, чтобы убрать JavaScript там, где он больше не нужен.

Заключение

CSS продолжает развиваться, и нововведения, такие как :has(), @container, scroll-driven анимации и многие другие, делают работу с этим языком более гибкой и мощной.

Эти возможности значительно расширяют горизонты для создания адаптивных и интерактивных веб-страниц.

Новые возможности позволяют значительно упростить многие задачи и улучшить производительность. Если вы ещё не пробовал эти фишки — обязательно попробуйте!

Вперед