Большой визуальный гайд по 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.
impwx
Классная статья, не знал про эту возможность. За иллюстрации отдельный респект