CSS Anchor Positioning в картинках

Большой визуальный гайд по CSS Anchor Positioning: от базовой привязки элементов до создания прочных соединителей и цепочек якорей.
CSS Anchor Positioning позволяет размещать абсолютно позиционированный элемент относительно других элементов — якорей; в Chrome эта возможность появилась в мае 2024 года, в версии 125.
У обычного position: absolute отступы top, right, bottom и left отсчитываются от границ содержащего блока (containing block), который обязательно определяется по цепочке родительских элементов. Anchor Positioning позволяет привязаться к элементу вне этой цепочки. Именно так устроены наши примеры: у якоря .anchor и зависимого элемента .target разные родители.
Разметка примеров выглядит так:
<main class="stage">
<div class="work-field">
<div class="anchor"></div>
</div>
<div class="parts-field">
<div class="target"></div>
</div>
</main>Мы будем связывать элементы с классами .anchor и .target. Обратите внимание: они находятся в разных родительских элементах.
1. Позиционирование через position-area
Вот исходное состояние: элементы .anchor и .target, которые мы будем связывать, пока находятся каждый внутри своего контейнера — как и ожидается.
.anchor {
/* Имя пока не задано. */
}
.target {
position: absolute;
}
Чтобы связать элементы через position-area, якорю задаём имя свойством anchor-name, а заякорённому элементу — это же имя в position-anchor и область размещения в position-area.
.anchor {
anchor-name: --a1;
}
.target {
position: absolute;
position-anchor: --a1;
position-area: right;
}Элемент переместился к якорю и расположился справа от него.

Если поменять расположение родительских контейнеров, привязка сохранится: элемент по-прежнему будет справа от якоря.

Браузер сам пересчитывает положение элемента относительно якоря: сначала рассчитывает новые границы якоря, затем размещает элемент в выбранной области справа от него.
position-anchor — выбор якоря
Поэкспериментируем с выбором якоря и посмотрим, как меняется положение элемента. В примере три якоря с именами --a1, --a2 и --a3. Пока у .target задано position-anchor: none, он ни к одному из них не привязан и находится в своём контейнере.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.anchor--three { anchor-name: --a3; }
.target {
position: absolute;
position-anchor: none;
position-area: top;
}
Теперь по очереди зададим в position-anchor имена --a1, --a2 и --a3. Элемент окажется над выбранным якорем: размещение сверху задано свойством position-area: top.

position-area — выбор области размещения
Теперь поэкспериментируем с расположением элемента относительно одного якоря. В исходном состоянии .target привязан к --a1 и находится над ним — задано position-area: top.
.anchor {
anchor-name: --a1;
}
.target {
position: absolute;
position-anchor: --a1;
position-area: top;
}По очереди зададим в position-area значения top, right, bottom и left. Элемент расположится сверху, справа, снизу и слева от якоря соответственно.

Границы якоря и содержащего блока образуют сетку 3×3, в центре которой находится якорь. Значение top выбирает всю верхнюю полосу этой сетки, right — всю правую; bottom и left работают аналогично.
Внутри выбранной полосы действует стандартное выравнивание: элемент прижимается к якорю по одной оси и центрируется относительно него по другой. Поэтому на картинках он оказывается у середины соответствующей стороны якоря.
2. Позиционирование через anchor()
position-area задаёт общую область размещения относительно якоря, а anchor() позволяет точнее управлять положением элемента, связывая отдельные стороны. Сторону заякорённого элемента выбираем свойством top, right, bottom или left, а сторону якоря указываем внутри функции. Например, left: anchor(right) совмещает левую сторону элемента с правой стороной якоря.
Начнём с исходного состояния: элементы .anchor и .target находятся каждый в своём контейнере. Якорь уже выбран через position-anchor, но стороны ещё не связаны, поэтому .target остаётся на прежнем месте.
.anchor {
anchor-name: --a1;
}
.target {
position: absolute;
position-anchor: --a1;
left: auto;
top: auto;
}
Привяжем левую сторону элемента к правой стороне якоря. Для этого зададим left: anchor(right), оставив top: auto.
.anchor {
anchor-name: --a1;
}
.target {
position: absolute;
position-anchor: --a1;
left: anchor(right);
top: auto;
}Элемент сместился по горизонтали: его левая сторона теперь находится на пунктирной линии, продолжающей правую границу якоря. Положение по вертикали не изменилось.

Теперь привяжем верхнюю сторону элемента к верхней стороне якоря с помощью top: anchor(top). Привязку через left сохраним.
.anchor {
anchor-name: --a1;
}
.target {
position: absolute;
position-anchor: --a1;
left: anchor(right);
top: anchor(top);
}Элемент сместился по вертикали и оказался справа от якоря. Их верхние стороны совпали по высоте, а верхний левый угол элемента находится на пересечении пунктирных линий.

Поэкспериментируем со стороной якоря, указанной внутри anchor(). В верхнем ряду меняем аргумент функции у left: элемент перемещается по горизонтали. В нижнем — у top: элемент перемещается по вертикали. Вторую привязку в каждом случае сохраняем.

Обратите внимание на center: в left: anchor(center) через середину якоря проходит левая сторона элемента, а в top: anchor(center) — верхняя. Сам элемент при этом не центрируется. Пунктир на картинках продолжает отмечать правую и верхнюю границы якоря — по ним удобно сравнивать смещение.
right и bottom — привязка правой и нижней сторон
Попробуем привязать правую и нижнюю стороны элемента через right и bottom. Начнём с исходного состояния: якорь выбран, но у обоих свойств задано auto, поэтому элемент пока находится в своём контейнере.
.anchor {
anchor-name: --a1;
}
.target {
position: absolute;
position-anchor: --a1;
right: auto;
bottom: auto;
}
Чтобы связать правую сторону элемента с левой стороной якоря, зададим right: anchor(left), а для совмещения нижних сторон — bottom: anchor(bottom).
.anchor {
anchor-name: --a1;
}
.target {
position: absolute;
position-anchor: --a1;
right: anchor(left);
bottom: anchor(bottom);
}Элемент расположился слева от якоря, их нижние стороны совпали по высоте. На пересечении пунктирных линий теперь находится нижний правый угол элемента.

Теперь поменяем аргументы anchor(): в верхнем ряду у right зададим center и right, а в нижнем у bottom — center и top. Элемент переместится так, чтобы его правая или нижняя сторона совпала с выбранной координатой якоря. Вторая привязка сохранится.

anchor() — явный выбор якоря
Имя якоря можно указать прямо внутри anchor(). Посмотрим, как это работает на примере с тремя якорями. В исходном состоянии position-anchor закомментирован, стороны ещё не связаны, и .target находится в своём контейнере.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.anchor--three { anchor-name: --a3; }
.target {
position: absolute;
/* position-anchor: --a1; */
left: auto;
top: auto;
}
Чтобы привязать элемент к --a1, укажем это имя в обеих функциях: left: anchor(--a1 right) свяжет левую сторону элемента с правой стороной якоря, а top: anchor(--a1 top) — верхние стороны.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.anchor--three { anchor-name: --a3; }
.target {
position: absolute;
/* position-anchor: --a1; */
left: anchor(--a1 right);
top: anchor(--a1 top);
}Элемент расположился справа от --a1, их верхние стороны совпали по высоте. Привязка работает с закомментированным position-anchor: имя якоря задано в самих функциях.

Теперь по очереди укажем в обеих функциях имена --a2 и --a3. Элемент переместится к выбранному якорю, сохраняя расположение справа и выравнивание по верхней стороне.

Несколько элементов у одного якоря
К одному якорю можно привязать несколько элементов. В этом примере у якоря уже задано имя --a1, а элементы .target--one и .target--two пока находятся в отдельном контейнере без привязки.
.anchor--one {
anchor-name: --a1;
}
.target--one,
.target--two {
position: absolute;
}
В функциях anchor() обоих элементов укажем имя --a1, но выберем разные стороны якоря. Для первого зададим left: anchor(--a1 right) и top: anchor(--a1 top), для второго — left: anchor(--a1 left) и top: anchor(--a1 bottom).
.anchor--one {
anchor-name: --a1;
}
.target--one {
position: absolute;
left: anchor(--a1 right);
top: anchor(--a1 top);
}
.target--two {
position: absolute;
left: anchor(--a1 left);
top: anchor(--a1 bottom);
}Первый элемент расположился справа от якоря с выравниванием по верхней стороне, второй — снизу с выравниванием по левой. Пунктирные линии показывают, с какими границами якоря совпали стороны каждого элемента.

Разные якоря для сторон одного элемента
Стороны одного элемента можно привязать к разным якорям, указав разные имена в anchor(). В исходном состоянии у нас два якоря — --a1 и --a2, а элемент с отмеченными углами пока находится в своём контейнере.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.target {
position: absolute;
right: auto;
bottom: auto;
}
Привяжем правую сторону элемента к левой стороне --a2 через right: anchor(--a2 left), а нижнюю — к верхней стороне --a1 через bottom: anchor(--a1 top).
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.target {
position: absolute;
right: anchor(--a2 left);
bottom: anchor(--a1 top);
}Элемент переместился: его правая сторона находится на вертикальном пунктире от --a2, а нижняя — на горизонтальном пунктире от --a1.

Положение элемента теперь зависит от двух якорей: --a2 задаёт его положение по горизонтали, а --a1 — по вертикали.
Чувствуете? Кажется, начинается…
Мы только что привязали разные стороны элемента к разным якорям. А если пойти дальше и привязать к ним два противоположных угла? Один угол — к первому якорю, другой — ко второму. Сам элемент тогда растянется между ними…
Подождите. Это же С-В-Я-З-Ь между элементами!
Та самая, которая будет тянуться от одного элемента к другому и подстраиваться под их положение. Соединители для схем, стрелки между карточками, линии между узлами. И всё это на CSS, без JavaScript для расчёта координат.
3. Суперприём: прочная связь между элементами
Начнём с двух якорей --a1 и --a2 и отдельного блока .route--one, который превратим в связь между ними. Пока его стороны не привязаны, а небольшой прямоугольник виден благодаря заранее заданным минимальным размерам. Ширину и высоту оставим автоматическими, чтобы блок мог растягиваться между якорями.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
position: absolute;
left: auto;
right: auto;
top: auto;
bottom: auto;
}
Сначала растянем блок по горизонтали: привяжем его левую сторону к правой стороне --a1, а правую — к левой стороне --a2.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
position: absolute;
left: anchor(--a1 right);
right: anchor(--a2 left);
top: auto;
bottom: auto;
}Блок растянулся между двумя вертикальными пунктирными линиями: они продолжают выбранные стороны якорей. Высота и положение по вертикали пока не изменились.

Теперь зададим вертикальные привязки: верхнюю сторону блока совместим с серединой --a1 по высоте, а нижнюю — с серединой --a2.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
position: absolute;
left: anchor(--a1 right);
right: anchor(--a2 left);
top: anchor(--a1 center);
bottom: anchor(--a2 center);
}Блок растянулся и по вертикали. Его верхний левый угол теперь находится у середины правой стороны первого якоря, а нижний правый — у середины левой стороны второго. Получили то, что задумали: два противоположных угла привязаны к разным якорям. Растяжение между заданными сторонами — стандартное поведение абсолютно позиционированного блока с автоматическими шириной и высотой.

Оформляем связь
Прямоугольник между якорями уже есть. Теперь нарисуем внутри него линию с помощью фонового изображения. Для оформления у блока подготовлен отдельный класс .route--bg; пока фона нет, видна только светлая подложка.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
position: absolute;
left: anchor(--a1 right);
right: anchor(--a2 left);
top: anchor(--a1 center);
bottom: anchor(--a2 center);
}
.route--bg {
background-image: none;
background-repeat: no-repeat;
background-position: center;
}
Добавим в фон SVG размером 100×100 с линией от верхнего левого угла к нижнему правому.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
position: absolute;
left: anchor(--a1 right);
right: anchor(--a2 left);
top: anchor(--a1 center);
bottom: anchor(--a2 center);
}
.route--bg {
background-image: url("images/route.svg");
background-repeat: no-repeat;
background-position: center;
}Линия появилась в центре блока, но до якорей пока не дотягивается: изображение сохранило свои размеры 100×100.

Растянем фон на весь блок с помощью background-size: 100% 100%.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
position: absolute;
left: anchor(--a1 right);
right: anchor(--a2 left);
top: anchor(--a1 center);
bottom: anchor(--a2 center);
}
.route--bg {
background-image: url("images/route.svg");
background-repeat: no-repeat;
background-position: center;
background-size: 100% 100%;
}Теперь линия соединяет якоря: её начало и конец совпали с привязанными углами блока.

За растяжение фона отвечает обычный background-size. В самом SVG задано preserveAspectRatio="none", чтобы линия растягивалась вместе с блоком по обеим осям.
Если передвинуть один из якорей, блок изменит положение и размеры, а фоновая линия растянется вместе с ним. Посмотрим на нескольких вариантах:

Линия во всех вариантах остаётся соединённой с якорями. Браузер сам пересчитывает границы блока, а фон заполняет его целиком.
4. Суперприём: цепочка якорей
Наш маршрут уже привязан к двум якорям. А что, если теперь привязать к самому маршруту кораблик и разместить его посередине? Получится цепочка: якоря → маршрут → кораблик. Давайте соберём её и посмотрим, будет ли кораблик следовать за маршрутом, когда мы передвинем якоря.
В исходном состоянии маршрут соединяет два якоря, а кораблик находится в своём контейнере. У маршрута пока нет имени, к которому можно привязать кораблик.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
anchor-name: none;
}
.ship {
position: absolute;
position-anchor: none;
position-area: none;
}
У нас есть два способа привязки: через position-area и через anchor(). Здесь нужно разместить кораблик по центру блока маршрута, поэтому выберем position-area: для этой задачи достаточно указать центральную область. Привязка отдельных сторон через anchor() нам сейчас не нужна.
Вспомним три свойства: маршруту зададим имя через anchor-name: --r1, а кораблику — position-anchor: --r1 для выбора маршрута и position-area: center для размещения по центру.
.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }
.route--one {
anchor-name: --r1;
}
.ship {
position: absolute;
position-anchor: --r1;
position-area: center;
}Кораблик переместился в центр маршрута. Цепочка сработала: маршрут привязан к двум якорям, а кораблик — к маршруту.
Светлая подложка показывает границы блока, по которому центрируется кораблик. В нашем рисунке линия проходит через центр этого блока, поэтому кораблик оказался прямо на ней.

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

Примечание. В цепочке каждый якорь должен быть рассчитан раньше элемента, который к нему привязан. Поэтому замкнуть цепочку, привязав элементы друг к другу, не получится.
В нашем примере маршрут и кораблик абсолютно позиционированы относительно общего содержащего блока
.stage, хотя находятся у разных родителей. Для такой пары важен порядок в HTML: блок маршрута должен идти раньше кораблика. Это следует из условий допустимости якоря в спецификации.
Добавляем второй маршрут
А как добавить ещё один якорь и ещё один маршрут? Добавим два элемента в конец .work-field. Так оба маршрута будут находиться в разметке раньше кораблика:
<div class="work-field">
<!-- Первые два якоря и первый маршрут -->
<div class="anchor anchor--three"></div>
<div class="route route--bg route--two"></div>
</div>
<div class="parts-field">
<div class="parts-field-cell">
<div class="ship ship--one"></div>
</div>
</div>Новый маршрут пока не привязан, а кораблик остаётся на первом.

Повторим знакомые шаги: дадим третьему якорю имя --a3 и привяжем стороны второго маршрута к --a2 и --a3:
.anchor--three { anchor-name: --a3; }
.route--two {
position: absolute;
left: anchor(--a2 right);
right: anchor(--a3 left);
top: anchor(--a2 center);
bottom: anchor(--a3 center);
}Второй маршрут соединил второй и третий якоря.

Теперь переместим на него кораблик. Дадим маршруту имя --r2 и укажем его в position-anchor у кораблика. Заданное ранее position-area: center сохранится:
.route--two {
anchor-name: --r2;
}
.ship {
position-anchor: --r2;
}Кораблик переместился в центр второго маршрута.

И всё это — на CSS. И всё это — уже сейчас!
Теперь понятно, почему все так ждали анкоров? В мае 2024 года они появились в Chrome, а с января 2026-го поддерживаются актуальными версиями всех основных браузеров. Если ваш проект ориентирован на эти версии, такую красоту уже можно делать в продакшне. Уберём вспомогательные подложки и просто полюбуемся.

Двигаем якоря, переключаем кораблик между маршрутами — и все связи сохраняются. Линии тянутся за якорями, кораблик держится в центре выбранного маршрута. Без единой строчки JavaScript для пересчёта координат.

А что вы уже делаете на анкорах? Показывайте свои примеры в комментариях — интересно посмотреть, где ещё пригодились такие связи.
Подписывайтесь на мой телеграм‑канал «CSS Боль», если хотите ещё больше интересного современного CSS.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.