CSS Anchor Positioning и Popover API: Больше никакого JS для попапов в 2026 году
前端工程
CSS Anchor Positioning: «Прокламация освобождения» для фронтенд-разработчиков
Каждый фронтенд-разработчик испытывал эту боль: использовать JS для вычисления позиций тултипов, обрабатывать выход за границы, слушать события scroll и resize... Теперь CSS может справиться со всем этим нативно.
Исторический обзор: в 2016 году мы использовали Popper.js, в 2020 — Floating UI, а в 2026 у нас наконец-то есть нативное CSS-якорное позиционирование. После 10 лет позиционирование попапов больше не является задачей JS.
Эволюция от JS-позиционирования к CSS-позиционированию
2016 jQuery offset() + ручные вычисления
Пересчёт при каждом scroll/resize
2020 Floating UI / Popper.js
Auto-flip, offset, обнаружение границ
Но всё ещё требует JS init и destroy
2024 CSS Anchor Positioning (Chrome 125+)
Чистое CSS-декларативное позиционирование
Браузер обрабатывает границы и скроллинг нативно
2026 Anchor Positioning + Popover API
Чистые CSS-попапы: toggle без JS, позиционирование без JS
Полное декларативное решение для попапов
anchor-name и position-anchor
Базовое использование
<div class="card">
<button class="trigger">Больше действий</button>
<div class="dropdown">Содержимое выпадающего меню</div>
</div>
.trigger {
anchor-name: --my-anchor;
}
.dropdown {
position: fixed;
position-anchor: --my-anchor;
top: anchor(bottom);
left: anchor(left);
}
Справка по функции anchor()
.tooltip {
position: fixed;
position-anchor: --trigger-anchor;
/* Относительно краёв якоря */
top: anchor(bottom);
bottom: anchor(top);
left: anchor(left);
right: anchor(right);
/* Центральные точки якоря */
left: anchor(50%);
top: anchor(50%);
/* С отступом */
top: calc(anchor(bottom) + 8px);
left: calc(anchor(left) - 4px);
}
position-area: Позиционирование по сетке девяти ячеек
Синтаксис девяти ячеек
┌─────────────┬─────────────┬─────────────┐
│ top left │ top center│ top right │
│ │ │ │
├─────────────┼─────────────┼─────────────┤
│ center left │ center │ center right│
│ │ │ │
├─────────────┼─────────────┼─────────────┤
│ bottom left │bottom center│ bottom right│
│ │ │ │
└─────────────┴─────────────┴─────────────┘
.tooltip-below {
position-area: bottom center;
}
.tooltip-right {
position-area: center right;
}
.dropdown-below-left {
position-area: bottom left;
}
position-fallback и многоуровневая стратегия фоллбэка
Авто-переворот при выходе за границы
.dropdown {
position: fixed;
position-anchor: --trigger;
/* Предпочтительно: под якорем */
top: anchor(bottom);
left: anchor(left);
position-fallback: --dropdown-fallback;
}
@position-fallback --dropdown-fallback {
/* Фоллбэк 1: над якорем */
@try {
bottom: anchor(top);
left: anchor(left);
}
/* Фоллбэк 2: под якорем, выравнивание по правому краю */
@try {
top: anchor(bottom);
right: anchor(right);
}
/* Фоллбэк 3: над якорем, выравнивание по правому краю */
@try {
bottom: anchor(top);
right: anchor(right);
}
}
Popover API: Нативный слой попапов
Базовое использование
<button popovertarget="my-popover">Открыть Popover</button>
<div id="my-popover" popover>
<p>Это содержимое нативного Popover</p>
<button popovertarget="my-popover" popovertargetaction="hide">Закрыть</button>
</div>
[popover] {
position: fixed;
inset: unset;
margin: 0;
padding: 16px;
border: 1px solid #e5e7eb;
border-radius: 8px;
background: white;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
}
Комбинация Popover + Anchor
<button class="menu-trigger" popovertarget="menu-popover">Меню</button>
<div id="menu-popover" popover class="menu-dropdown">
<a href="/profile">Профиль</a>
<a href="/settings">Настройки</a>
<a href="/logout">Выход</a>
</div>
.menu-trigger {
anchor-name: --menu-anchor;
}
.menu-dropdown {
position-anchor: --menu-anchor;
position-area: bottom left;
margin: 4px;
}
anchor-size(): Автоматический размер на основе якорного элемента
.trigger {
anchor-name: --select-anchor;
}
.select-dropdown {
position: fixed;
position-anchor: --select-anchor;
top: anchor(bottom);
left: anchor(left);
/* Ширина выпадающего списка соответствует кнопке-триггеру */
width: anchor-size(width);
/* Или установить минимальную ширину */
min-width: anchor-size(width);
}
Практика: Типичные UI-компоненты на чистом CSS
Тултип
<span class="tooltip-trigger" tabindex="0">
Справка
<span class="tooltip-content" role="tooltip">Это справочный тултип</span>
</span>
.tooltip-trigger {
anchor-name: --tt-anchor;
position: relative;
cursor: help;
text-decoration: underline dotted;
}
.tooltip-content {
position: fixed;
position-anchor: --tt-anchor;
position-area: top center;
margin: 8px;
padding: 6px 10px;
background: #1f2937;
color: white;
border-radius: 4px;
font-size: 13px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s;
position-fallback: --tt-fallback;
}
.tooltip-trigger:hover .tooltip-content,
.tooltip-trigger:focus .tooltip-content {
opacity: 1;
}
@position-fallback --tt-fallback {
@try { position-area: bottom center; }
@try { position-area: center right; }
@try { position-area: center left; }
}
Выпадающее меню
<button class="dropdown-trigger" popovertarget="dd-menu">
Опции ▾
</button>
<div id="dd-menu" popover class="dropdown-menu">
<button class="dropdown-item">Редактировать</button>
<button class="dropdown-item">Копировать</button>
<button class="dropdown-item">Удалить</button>
</div>
.dropdown-trigger {
anchor-name: --dd-anchor;
}
.dropdown-menu {
position-anchor: --dd-anchor;
position-area: bottom left;
margin: 4px;
width: anchor-size(width);
min-width: 160px;
padding: 4px;
border-radius: 8px;
background: white;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
position-fallback: --dd-fallback;
}
.dropdown-item {
display: block;
width: 100%;
padding: 8px 12px;
border: none;
background: none;
text-align: left;
border-radius: 4px;
cursor: pointer;
}
.dropdown-item:hover {
background: #f3f4f6;
}
@position-fallback --dd-fallback {
@try { position-area: top left; }
}
Сравнение с Floating UI/Popper.js
| Измерение | CSS Anchor + Popover | Floating UI |
|---|---|---|
| Зависимость от JS | Ноль JS | 12KB gzip |
| Производительность | Нативная браузерная | JS-вычисления |
| Декларативность | CSS-декларативный | JS-императивный |
| Переворот при границах | position-fallback | middleware flip() |
| Отступ | margin/anchor() | middleware offset() |
| Стрелка | CSS-псевдоэлемент | SVG/HTML-элемент |
| Виртуальные элементы | Не поддерживается | Поддерживается |
| Динамический контент | Авто-следование | Требует update() |
| Поддержка браузеров | Chrome 125+ | Все браузеры |
| SSR-совместимость | Полностью совместим | Требует клиентский JS |
Когда вам всё ещё нужны JS-библиотеки
Сценарии, всё ещё требующие Floating UI:
1. Необходима поддержка старых браузеров (Safari < 17.5, Firefox < 131)
2. Необходимо позиционирование виртуальных элементов (следование за курсором)
3. Необходимы сложные цепочки middleware (autoUpdate, size, hide)
4. Требования декларативных обёрток для React/Vue
Сценарии, где CSS Anchor работает:
1. Тултипы, выпадающие меню, попапы и другие стандартные попапы
2. Необходимо поддерживать только современные браузеры
3. Стремление к нулевой JS-зависимости
4. Сценарии, чувствительные к производительности
Совместимость браузеров и полифиллы
Статус поддержки
| Браузер | Anchor Positioning | Popover API |
|---|---|---|
| Chrome 125+ | ✅ | ✅ |
| Edge 125+ | ✅ | ✅ |
| Safari 17.5+ | ✅ | ✅ |
| Firefox 131+ | ✅ | ✅ |
| iOS Safari 17.5+ | ✅ | ✅ |
Решения с полифиллами
<script src="https://cdn.jsdelivr.net/npm/@oddbird/css-anchor-positioning@0.3.0/dist/css-anchor-positioning.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@oddbird/popover-polyfill@0.4.0/dist/popover.min.js"></script>
Комбинация с @starting-style для анимации попапов
[popover] {
opacity: 0;
transform: translateY(-8px) scale(0.95);
transition: opacity 0.2s, transform 0.2s, display 0.2s allow-discrete;
overlay: none;
}
[popover]:popover-open {
opacity: 1;
transform: translateY(0) scale(1);
overlay: auto;
}
@starting-style {
[popover]:popover-open {
opacity: 0;
transform: translateY(-8px) scale(0.95);
}
}
Итоги
- CSS Anchor Positioning — это окончательное решение для позиционирования попапов — Ноль JS, декларативно, нативно в браузере
- Popover API делает переключение попапов тоже без JS — атрибут popovertarget решает всё в одну строку
- position-fallback решает проблему выхода за границы — Больше никаких JS-вычислений для переворота
- Все основные браузеры поддерживают в 2026 году — Безопасно использовать в продакшене
После 10 лет позиционирование попапов наконец-то вернулось туда, где ему место — в CSS. Это не постепенное улучшение — это смена парадигмы, точно так же как Flexbox заменил float-макеты.
Попробуйте эти локальные браузерные инструменты — регистрация не требуется →
#CSS锚点定位#Anchor Positioning#Popover API#弹出层#CSS