position: сдвиг, якорь и значок, улетевший из карточки
Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.
В нормальном потоке коробка стоит там, куда её поставил порядок разметки. Свойство position разрешает сдвинуть элемент или вовсе вынуть его из потока и поставить по координатам: значок — в угол карточки, тень — поверх фотографии, шапку — над прокручиваемым текстом. Координаты всегда отсчитываются от какого-то края, и главная ошибка здесь одна — перепутать, от какого. Значок, которому велели стоять в углу карточки, уезжает в угол страницы, и это не своеволие браузера, а отсутствующая строка в стилях. Разберём сдвиг, привязку к якорю и наложение слоёв, а два режима, которые проявляются только при прокрутке, — словами.
У каждого элемента есть свойство position, и по умолчанию оно static: элемент стоит в потоке, а свойства top, right, bottom и left на него не действуют вовсе. Остальные значения — relative, absolute, fixed и sticky — делают элемент позиционированным, и эти четыре свойства начинают работать. Само свойство position — Baseline widely с 29.01.2018.
У relative числа читаются как расстояние от прежнего места: top: 20px — «верхний край на 20 пикселей ниже, чем был», left: 30px — «левый край на 30 правее». Отрицательные числа двигают в обратную сторону. Слово «relative» здесь означает именно «относительно себя прежнего», а не относительно родителя, — и на этом путаются чаще всего.
Три блока высотой 40 стоят стопкой, второму даны position: relative, top: 20px и left: 30px. Прогноз: на каких y и x окажутся три блока? Главный вопрос здесь не про второй блок, а про третий: опустится ли он вслед за сдвинутым соседом?
relative двигает только картинку: место в потоке остаётся за элементом, и соседи раскладываются так, будто сдвига не было. Поэтому второй блок наезжает на третий — с 80 по 100 они перекрываются, — а справа вылезает за край сцены на те же 30 пикселей.top: 20px опускает элемент, а не поднимает: это расстояние от верхнего края его прежнего места. Поднять на 20 — top: -20px.
Двигать блоки через relative приходится редко: если нужен просвет, берут margin, и соседи подвинутся следом. Главная работа relative — вовсе без top и left. Такой элемент стоит на своём месте, но уже позиционирован, и это делает его якорем для absolute.
position: absolute вынимает элемент из потока совсем: соседи смыкаются, будто его нет, родитель не учитывает его в своей высоте, а сам элемент встаёт по координатам. Именно так делают значок в углу карточки и тень поверх фотографии. Как устроен нормальный поток, из которого элемент выпадает, разбирает предыдущая статья главы.
От чего считаются координаты? Браузер поднимается от элемента по предкам и берёт первого позиционированного — с любым position, кроме static. Этот предок и становится якорем: top: 0; right: 0 — его правый верхний угол, считая от внутреннего края рамки. Если позиционированных предков нет ни одного, якорем становится сама страница — прямоугольник размером с окно в её начале. Отсюда и уехавший значок: он честно встал в угол, только угол оказался не карточки, а страницы.
Ниже две одинаковые карточки 200 × 50 с отступом сверху 20. В каждой лежит значок 20 × 20 с position: absolute; top: 0; right: 0 — «в правый верхний угол». Отличаются карточки одним: первой, с классом anchor, дано position: relative. Прогноз: на каких y встанут карточки и значки, на каком x — значок первой карточки и какой высоты будут карточки?
y 20, как у самой карточки, x 180 = 200 − 20. А значок второй оказался на −10, выше сцены. Ни у второй карточки, ни выше неё позиционированных предков нет, и top: 0 привязал значок к верхнему краю страницы — рамки примера. Сцена прибора начинается на 10 пикселей ниже: это внутренний отступ <body> песочницы. По горизонтали второй значок прижат к правому краю рамки, поэтому его x прибор не спрашивает: число зависело бы от ширины вашего окна.Значок в примере — <span>, строчный элемент, а на такие ширина с высотой обычно не действуют. Но absolute делает элемент блочным, поэтому размер 20 × 20 сработал. Это побочный эффект, о котором полезно помнить: вытащенный из потока элемент перестаёт вести себя как строчный.
Значок улетел в угол страницы? У предка, от которого он должен считать, нет position. Дайте этому предку position: relative без top и left — сам предок останется на месте.
Координаты можно задать с двух противоположных сторон сразу, и тогда размер элемента определяет якорь: left: 20px; right: 20px растягивает элемент на ширину якоря без 20 пикселей с каждой стороны. Короткая запись inset: 0 ставит все четыре стороны в ноль, и элемент ложится ровно на якорь, как тень поверх фотографии. inset — Baseline widely с 26.10.2023.
В карточке 200 × 100 лежат тень и полоска. Прогноз: какие ширину и высоту окажутся у обеих и на каком y встанет полоска? Здесь работает правило двух противоположных сторон, о котором только что шла речь, — размеры элементов задаёт якорь.
y — 80: нижний край на 10 выше низа карточки, 100 − 10 − 10. Без width и без пары left и right абсолютный элемент поступает иначе, чем блок в потоке: не растягивается во всю ширину, а сжимается по содержимому.В части II подсказку к кнопке ставит скрипт, и координаты для неё приходится считать самим. Подсказка на absolute без позиционированного предка привязана к странице, поэтому числа для неё берут от начала документа, с учётом прокрутки. Как это делается, разбирает статья о координатах относительно окна и документа.
Когда позиционированные элементы перекрываются, сверху по умолчанию тот, кто позже в разметке: браузер рисует их по порядку, и следующий ложится поверх предыдущего. А все позиционированные рисуются поверх обычных. Поэтому сдвинутый блок из первого раздела лёг на третий, хотя в разметке стоит раньше. Порядок меняет z-index: целое число, и чем оно больше, тем элемент выше. Умолчание — auto: элемент рисуется на уровне ноль, в порядке разметки.
Картинку прибор не видит, поэтому о том, кто сверху, спрашивают отдельно — topAt(x, y): она отвечает текстом элемента, который лежит сверху в точке сцены, x пикселей от её левого края и y от верхнего. Ниже две сцены высотой 40, в каждой блоки A и B шириной 60: A у левого края, B на 30 правее, и с 30 до 60 пикселей они перекрываются. Во второй сцене у A есть z-index: 1. Прогноз: чей текст окажется сверху в точке 45, 20 первой сцены и в точке 45, 60 второй?
z-index: 1 больше, чем auto у B, и порядок в разметке уже не важен. У элемента со static z-index молча не работает: сначала position, потом число. Исключение — дети flex- и grid-контейнеров, до которых глава ещё дойдёт: у них z-index работает и без position.Отсюда соблазн: не помог z-index: 10 — поставить 999. Но числа сравниваются не по всей странице. Позиционированный элемент с числом в z-index создаёт контекст наложения: всё его содержимое рисуется вместе с ним, одним слоем, а числа детей решают только, кто сверху внутри этого слоя. Внешний мир видит один слой и сравнивает его с другими по его же числу. В сценах ниже меню с z-index: 999 лежит в левой карточке и выступает на правую, у которой z-index: 2. Отличие одно: в первой сцене у левой карточки есть z-index: 1, во второй — нет. Прогноз: чей текст окажется сверху в точке, где меню заходит на правую карточку, в каждой из сцен?
z-index: то же делают, например, прозрачность opacity меньше 1 и трансформация transform.z-index: 9999 не помогает? Число заперто в слое предка. Найдите предка с z-index, opacity или transform и поднимайте его, а не ребёнка.
Есть слой выше любого числа. Модальное окно, открытое showModal(), браузер кладёт в верхний слой — над всей страницей, какой бы z-index ни стоял у остальных. Окно, показанное атрибутом open, туда не попадает и спорит за место по общим правилам. Чем эти два окна различаются, разбирает первая глава — окно dialog и модальный режим.
position: fixed похож на absolute: элемент выпадает из потока и встаёт по координатам. Но якорь у него не предок, а окно браузера — его видимая часть. Страницу прокручивают, а элемент стоит на месте: так держатся шапка, кнопка «наверх» и окошко чата. Места в потоке у него нет, поэтому начало страницы прячется под фиксированной шапкой, и первому блоку дают отступ сверху на её высоту.
У fixed есть ловушка. Если у кого-то из предков задана трансформация transform или фильтр filter, например размытие, якорем становится этот предок, и «фиксированный» элемент поедет при прокрутке вместе с ним. Трансформации встретятся в статье главы о переходах — и transform, и отдельные свойства translate и scale. Все они перехватывают fixed одинаково, так что эту связь стоит помнить заранее.
position: sticky — гибрид. Пока до элемента не докрутили, он стоит в потоке, как relative. Когда при прокрутке его край доходит до порога, например top: 0, элемент прилипает к краю и держится, пока не кончится его родитель, а дальше родитель уводит его с собой. Так ведут себя заголовки разделов, которые висят сверху, пока читается раздел. Без порога в top или bottom при прокрутке вниз элемент не прилипнет. position: sticky — Baseline widely с 19.03.2022.
Порог отсчитывается от окна, только если выше по дереву нет предка с overflow: auto или overflow: hidden. Первое значение даёт блоку свою полосу прокрутки, второе обрезает всё, что в блок не влезло. С любым из них элемент прилипает внутри этого предка, и если сам предок не прокручивается, прилипания не видно вовсе.
Прибор здесь бессилен: оба режима проявляются только при прокрутке, а рамку примера он не прокручивает. Проверьте на своей странице: длинный текст и заголовок с position: sticky; top: 0 — хватит одного правила.
Привязку к любому элементу страницы, а не только к предку, обещает anchor positioning: подсказка встаёт прямо под кнопкой, где бы та ни стояла. Но это Baseline limited: основные свойства есть во всех основных браузерах лишь с 13.01.2026, а часть значений position-visibility есть только в Safari — не рассчитывайте на неё.
В части II координаты фиксированного элемента и координаты щелчка мыши окажутся в одной системе — от угла окна, и пересчитывать между ними ничего не придётся. Об этом — статья о событиях мыши и координатах clientX.
- По умолчанию
position: static: элемент стоит в потоке, иtop,right,bottom,leftна него не действуют. Любое другое значение делает элемент позиционированным. relativeсдвигает только картинку: место в потоке остаётся прежним, соседи стоят где стояли, а сдвинутый элемент может на них наехать. Без координатrelativeничего не двигает, но превращает элемент в якорь.absoluteвыпадает из потока и считает координаты от ближайшего позиционированного предка, от внутреннего края его рамки. Если такого предка нет, якорь — сама страница, и значок улетает из карточки в её угол.- Две противоположные координаты растягивают абсолютный элемент между краями якоря, а
inset: 0кладёт его ровно на якорь. - Сверху тот, кто позже в разметке, а позиционированные — поверх обычных.
z-indexработает у позиционированных элементов, и его число заперто в контексте наложения предка: 999 внутри слоя с числом 1 проиграет соседу с числом 2. fixedпривязан к окну и не уезжает при прокрутке, если у предков нетtransformилиfilter.stickyстоит в потоке, пока не дойдёт до порога, и прилипает в пределах родителя.
1/4У блока в стилях top: 30px и left: 30px, а position не задан. Где он окажется?
2/4Значок с position: absolute; top: 0; right: 0 лежит в .card без position, а та — в .page с position: relative. От чьего угла он встанет?
3/4Второму из трёх блоков стопки дали position: relative; top: 50px. Что станет с третьим?
4/4У карточки position: relative; z-index: 1, в ней меню с position: absolute; z-index: 999. Меню выступает на соседнюю карточку с position: relative; z-index: 2. Кто сверху там, где они перекрываются?
Значок не в том углу
Значок должен стоять в правом верхнем углу фотографии, а стоит в другом месте. Сначала, не запуская пример, посчитайте, какие x и y прибор напечатает у фотографии и значка, и только потом запустите. Затем допишите одно объявление, чтобы значок встал в угол фотографии. Правило .card не трогайте.
У карточки ширина 240 и внутренний отступ 20, поэтому фотография стоит на 20, 20 и шириной 200. У самой .photo position нет, и браузер ищет якорь выше — до .card. Рамки у карточки нет, её внутренний край — вся коробка, так что значок встаёт в угол карточки: x 240 − 20 = 220 и y 0.
Теперь ближайший позиционированный предок — фотография. Прибор напечатает .photo, .badge → x: 20px, 200px и .photo, .badge → y: 20px, 20px: значок встал на 20 + 200 − 20 = 200 и на верхний край фотографии. Сама фотография не сдвинулась: relative без координат только делает её якорем.
Кнопка под тенью
Поверх баннера лежит тень и накрывает кнопку: topAt в точке на кнопке находит тень, и щелчок мыши тоже достался бы ей. Автор стилей пробовал поднять кнопку через z-index: 10, но это не помогло. Объясните почему и допишите в правило .buy одно объявление, чтобы в той же точке сверху оказалась кнопка.
У кнопки position не задан, то есть static, а у такого элемента z-index не работает. Тень позиционирована, поэтому рисуется поверх обычных элементов, да ещё и стоит в разметке позже.
Прибор напечатает в точке 20, 10: Купить. relative без координат кнопку не сдвигает, но делает её позиционированной, и z-index: 10 поднимает её над тенью с её auto. Есть и другой путь — сделать тень прозрачной для мыши объявлением pointer-events: none в правиле .shade. Нарисована тень останется поверх кнопки, но щелчки и topAt пройдут сквозь неё.
Посчитайте до запуска
Карточка сдвинута через relative на 10 пикселей вниз и вправо. В ней ярлык, прижатый к левому нижнему углу, а под карточкой — ещё один блок. Не запуская пример, посчитайте четыре числа: y карточки, y и x ярлыка и y следующего блока. Потом запустите.
В потоке карточка стоит на 0, а top: 10px сдвигает её картинку на 10: y 10. Сдвинутая карточка — позиционированный предок ярлыка, и ярлык едет вместе с ней. Его нижний край совпадает с нижним краем карточки, 10 + 60 = 70, значит, y ярлыка 70 − 20 = 50, а x — 10, левый край карточки. Следующий блок сдвига не заметил: место карточки в потоке — с 0 по 60, и он стоит на 60. Прибор напечатает .card → y: 10px, .tag → y: 50px, .tag → x: 10px и .next → y: 60px.