Пару месяцев назад автора этого материала спросили о проблеме, которая возникла именно из-за этой строки CSS. В решении свойства position
не оказалось вообще.
Пока у нас стартует новый поток курса по фронтенду, рассказываем о случаях, когда position: absolute
вполне заменим современным CSS.
Введение
Вернёмся на 5 лет назад. Тогда CSS flexbox был новинкой и не мог работать на 100 %, а CSS grid не поддерживался. Мы работали с позиционированием CSS. Но сегодня проблемы тех лет решаются при помощи CSS flexbox или grid.
Наложение карточек
Когда у нас есть карточка, содержащая текст поверх изображения, мы часто используем position: absolute
для размещения содержимого поверх изображения. Это больше не нужно при использовании CSS grid.
Вот типичная карточка с текстом над изображением:
<article class="card">
<div class="card__thumb">
<img src="assets/mini-cheesecake.jpg" alt="">
</div>
<div class="card__content">
<h2><a href="#">Title</a></h2>
<p>Subtitle</p>
</div>
</article>
Чтобы разместить содержимое над изображением, нужно позиционировать .card__content
абсолютно:
.card {
position: relative;
}
.card__content {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0) bottom/100% 60% no-repeat;
padding: 1rem;
}
Нет ничего плохого в position: absolute
из примера выше, но почему не написать проще? Первый шаг — добавить display: grid
к компоненту card. Задавать столбцы или строки не нужно:
.card {
position: relative;
display: grid;
}
.card__content {
display: flex;
flex-direction: column;
justify-content: flex-end;
}
По умолчанию CSS grid создаёт строки автоматически, на основе контента. В нашей карточке основных элементов два, и поэтому получится две строки контента. Чтобы перекрыть содержимое с изображением, нужно разместить оба элемента на первой области сетки:
.card__thumb,
.card__content {
grid-column: 1/2;
grid-row: 1/2;
}
А лучше воспользоваться сокращением grid-area
:
.card__thumb,
.card__content {
grid-area: 1/2;
}
Наконец, мы также можем использовать grid-area: 1/-1
. -1
— последний столбец и строку в сетке, поэтому она всегда будет простираться до конца столбца или строки.
Метка карточки (must try)
Хочется расширить предыдущий пример и включить тег в верхнюю левую область карточки. Для этого нам нужно использовать ту же технику — grid-area: 1/-1
, но не хочется, чтобы тег заполнил всю карточку.
Чтобы исправить проблему заполнения, нам нужно указать метке выравнивание по началу контейнера:
.card__tag {
align-self: start;
justify-self: start;
/* Other styles */
}
Познакомьтесь с меткой, которая позиционируется над своим родителем без position: absolute
:
Раздел Hero
Ещё один идеальный вариант — раздел hero с контентом, который перекрывает изображение.
Похоже на пример с карточкой, но с другой стилизацией содержимого. Прежде чем перейти к современному способу, на минуту вспомним, как это делается при помощи абсолютного позиционирования. У нас есть три слоя:
изображение;
градиентное наложение;
контент.
Это можно сделать разными способами. Если изображение исключительно декоративное, то подходит background-image
. В противном случае можно воспользоваться <img>
:
.hero {
position: relative;
min-height: 500px;
}
.hero__thumb {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
/* The overlay */
.hero:after {
content: "";
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: #000;
opacity: 0.5;
}
.hero__content {
position: absolute;
left: 50%;
top: 50%;
z-index: 1;
transform: translate(-50%, -50%);
text-align: center;
}
Вот так hero реализуется при помощи абсолютного позиционирования. Но можно сделать лучше.
Прежде всего добавим к hero
свойство display: grid
. И применим тот же принцип, что в компоненте карточки, то есть пропишем grid-area: 1/-1
в каждом прямом потомке элемента hero
.
К сожалению, чтобы .hero__thumb
работал, высоту hero придётся фиксировать. Если потомок элемента имеет свойство height: 100%
, то его родитель должен иметь явное фиксированное свойство height
, а не min-height
.
.hero {
display: grid;
height: 500px;
}
.hero__content {
z-index: 1; /* [1] */
grid-area: 1/-1;
display: flex;
flex-direction: column;
margin: auto; /* [2] */
text-align: center;
}
.hero__thumb {
grid-area: 1/-1;
object-fit: cover; /* [3] */
width: 100%;
height: 100%;
min-height: 0; /* [4] */
}
.hero:after {
content: "";
background-color: #000;
opacity: 0.5;
grid-area: 1/-1;
}
Пройду по отмеченным строкам, они важны:
Мы можем использовать
z-index
для элементов grid или flex. При этом нет необходимости добавлятьposition: relative
.Поскольку
.hero__content
— элемент grid, свойствоmargin: auto
центрирует его по горизонтали и вертикали.Не забудьте включить
object-fit: cover
при работе с изображениями.Я использовал
min-height: 0
для изображения на случай, если оно окажется большим. Это нужно, чтобы заставить grid соблюдатьheight: 100%
и не делать изображение больше, чем секция hero, когда используется огромное изображение. Подробнее об этом читайте в статье о минимальном размере контента в CSS grid.
Заметили, что решение стало намного чище?
CSS display: contents
Я думаю, что это первый реальный случай использования display: contents
, который был полезен. В примере ниже у нас есть содержимое и изображение.
Вот HTML-код примера:
<div class="hero">
<div class="hero__content">
<h2><!-- Title --></h2>
<p><!-- Desc --></p>
<a href="#">Order now</a>
</div>
<img src="recipe.jpg" alt="">
</div>
До сих пор ничего странного. На мобильных устройствах хочется добиться такого расположения:
Обратите внимание: изображение теперь расположено между заголовком и описанием. Как решить эту проблему? Можно подумать, что разметку нужно изменить так:
<div class="hero">
<div class="hero__content">
<h2><!-- Title --></h2>
<img src="recipe.jpg" alt="">
<p><!-- Desc --></p>
<a href="#">Order now</a>
</div>
</div>
Этот код может работать согласно ожиданиям, а на десктопе нужно расположить <img>
справа. Он работает, но есть решение с display: contents
и оно проще. Чтобы показать его, вернёмся к исходной разметке:
<div class="hero">
<div class="hero__content">
<h2><!-- Title --></h2>
<p><!-- Desc --></p>
<a href="#">Order now</a>
</div>
<img src="recipe.jpg" alt="">
</div>
Обратите внимание: содержимое обёрнуто внутри .hero__content
. Как сообщить браузеру о нашем желании, чтобы <h2>
, <p>
и <a>
стали прямыми потомками <img>
? При помощи display: contents
:
.hero__content {
display: contents;
}
Элемент .hero__content
теперь — скрытый призрак. Браузер будет парсить разметку вот так:
<div class="hero">
<h2><!-- Title --></h2>
<p><!-- Desc --></p>
<a href="#">Order now</a>
<img src="recipe.jpg" alt="">
</div>
И вот всё, что нам нужно сделать в CSS:
.hero {
display: flex;
flex-direction: column;
}
.hero__content {
display: contents;
}
.hero h2,
.hero img {
order: -1;
}
При правильном применении display: contents
— это мощная техника, позволяющая добиться того, что ещё несколько лет назад было невозможно. Конечно, мы хотим так же обращаться со стилями десктопа:
@media (min-width: 750px) {
.hero {
flex-direction: row;
}
.hero__content {
display: initial;
}
.hero h2,
.hero img {
order: initial;
}
}
Упорядочиваем элементы компонента карточки
Ниже код варианта карточки, где заголовок размещается в её верхней части. Посмотрим на HTML:
<article class="card">
<img src="thumb.jpg" alt="">
<div class="card__content">
<h3>Title</h3>
<p>Description</p>
<p>Actions</p>
</div>
</article>
Обратите внимание: у нас есть два прямых потомка card
: изображение thumb.jpg
и div
с классом card__content
. Как с такой разметкой расположить заголовок <h3>
вверху? Это можно сделать с помощью абсолютного позиционирования:
.card {
position: relative;
padding-top: 3rem;
/* Accommodate for the title space */
}
.card h3 {
position: absolute;
left: 1rem;
top: 1rem;
}
Однако это решение может не работать, когда заголовок слишком длинный.
Это происходит потому, что заголовок находится вне обычного потока, а значит, браузеру не важна его длина. При помощи display: contents
можно добиться большего:
.card {
display: flex;
flex-direction: column;
padding: 1rem;
}
.card__content {
display: contents;
}
.card h3 {
order: -1;
}
Это позволяет контролировать все дочерние элементы, при необходимости гибко настраивая их порядок свойством order
.
Есть небольшая проблема: мы добавили padding: 1rem
к родительскому элементу, поэтому изображение должно прилипать к краям. Вот как исправить ситуацию:
.card img {
width: calc(100% + 2rem);
margin-left: -1rem;
}
Центрирование
Можно встретить много шуток о том, что центрирование — это сложно. Но в наши дни центрировать элемент проще, чем когда-либо. Больше не нужно использовать position: absolute
с transform
. К примеру, для центрирования flex-элемента по горизонтали и по вертикали можно воспользоваться margin: auto
:
.card {
display: flex;
}
.card__image {
margin: auto;
}
Я написал полное руководство по центрированию в CSS.
Соотношение сторон изображения
Новое свойство CSS aspect-ratio
сегодня поддерживается во всех основных браузерах. Чтобы заставить бокс соблюдать определённое соотношение сторон, раньше мы использовали этот хак с padding
:
.card__thumb {
position: relative;
padding-top: 75%;
}
.card__thumb img {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
С aspect-ratio
соблюдать соотношения проще:
/* */
.card__thumb {
position: relative;
aspect-ratio: 4/3;
}
Подробности смотрите здесь.
Иногда лучше position: absolute
В этом примере содержимое (аватар, имя и ссылка) перекрывается обложкой карточки. У нас есть два варианта:
position: absolute
для обложки карточки;отрицательный
margin
для содержимого.
Посмотрим на два других решения. Какое из них больше подходит для нашего случая?
Использование абсолютного позиционирования
Так мы можем позиционировать прямоугольник абсолютно, а затем добавить padding: 1rem
к содержимому карточки:
.card__cover {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 50px;
}
.card__content {
padding: 1rem;
}
Таким образом, когда обложка карточки будет убрана, нам не придётся изменять CSS.
Использование отрицательного margin
В этом решении карточка не позиционируется абсолютно. Вместо этого содержимое имеет отрицательный margin
от верха:
.card__content {
padding: 1rem;
margin-top: -1rem;
}
Хотя это решение работает, когда обложка у карточки есть, оно может вызвать проблему:
Обратите внимание, что аватар находится за пределами своего родителя (карточки). Чтобы исправить это, изменим CSS и уберём отрицательный margin
:
.card--no-cover .card__content {
margin-top: 0;
}
Лучшим решением оказалось position: absolute
. Здесь это свойство избавляет от лишнего CSS.
Дополнительные материалы
Райан Маллиган опубликовал отличную статью о позиционировании оверлейного контента с помощью CSS-grid.
Стефани Эклз написала замечательную статью об использовании CSS-grid для построения разделов hero.
Разобраться в современном CSS вы сможете на наших курсах:
Узнать подробности акции
Другие профессии и курсы
Data Science и Machine Learning
Python, веб-разработка
Мобильная разработка
Java и C#
От основ — в глубину
А также
Комментарии (12)
mSnus
18.11.2021 06:53+10Вот увидел бы такой css, мысль была бы одна: "здесь творится какая-то чертова магия". Ради чего городить grid и включать довольно неочевидный display: content? Ради экономии пара строчек и погони за модой?
Пусть даже CSS и не язык программирования, но это код, а код должен быть читаемым и понятным тому, кто будет работать с ним после тебя.
И я бы очень не хотел оказаться на месте того, кто будет с этим всем разбираться через год- другой после того, как наворотивший всё это верстальщик свалит в другой стартап.
extempl
18.11.2021 08:26+1Ну скажем, с гридами в области решения hero секции подход интересный. Но вот `display: contents` - это и правда неочевидная фича и костыль. Зато стильно-модно-молодёжно.
Достаточно убрать `__content` вообще и использовать `order` as-is.
Exclipt
18.11.2021 11:19Хорошо иметь подобный уровень знаний, но его надо применять во благо. У вас в статье классический пример "усложнения кода ради великого ничего". У меня в команде так самые молодые любят писать, чтобы показать, какие они грамотные, не задумываясь о том, что другие молодые, которым это саппортить, могут быть не такие уж и грамотные, или вообще просто "Вася, оторвись на секунду от бэкэнда, надо картинку передвинуть".
amvasiljev
18.11.2021 13:13не стоит сжигать дом, ради того, чтобы поджарить пару яиц ))
но решение интересное
korsetlr473
18.11.2021 16:06+1Ну и треш....
Наложение , а не позиционирование алё
"почему не написать проще? Первый шаг — добавить
display: grid
к компоненту card." дальше даже не читал....ужас .
antsam
18.11.2021 23:55Лично я свалил с абсолютного позиционирования из-за бесконечных проблем с z-index.
Как только начинается position: relative, то он внезапно становится поверх обычных элементов и создает свой контекст наложения, потом появляются z-index-ы, потом приходится вручную продумывать и конфигурировать какой элемент над каким находится. Вот тогда и начинается ужас.
Намного проще отдать эти расчеты браузеру просто расположив элементы в правильном месте в DOM дереве.
До флексов и гридов от абсолютного позиционирования нельзя было уйти, но после их появелния у меня лично сомненией не возникло - не хочется повторять негативный опыт с z-index.
Ну и не знаю как сейчас, но года 4 назад были жуткие тормоза из-за "position: relative" на мобильніх устройствах и переписывали тормозные участки на флексы.
wadowad
19.11.2021 11:22Если у слоя, который должен быть позиционирован внизу, назначить z-index: -1, то не нужно будет у всего остального прописывать position: relative. Да, есть минусы такого решения. Например, у родителя не должно быть фона.
antsam
19.11.2021 00:04мне кстати запись
grid-area: 1/2;
неочень нравится и действительно не очень понятна
Я у себя делаю именованный grid-area:
.layers{ display: grid; grid-template: "layers" auto / auto; > *, > ::after, > ::before { grid-area: layers; } }
Сразу понятно что в этом элементе будут слои расположенные друг над другом.
XeL077
position: absolute; выглядит гораздо проще "магических чисел" в css. Конечно можно сделать миксин и все такое, но если нет проблем с композитными слоями не вижу причины переходить на гриды.
antsam
Может это дело привычки, но помоему
выглядит гараздо понятнее, чем заклинание:
При этом в первом случае у меня остался неисползуемый "transform", который я могу использовать например для анимации.
freekir
Вы абсолютно правы, дело привычки. Кому-то и хаки для ие были норм. А кому-то эти дроби в гридах мутной темой кажутся.
Как по мне главное чтобы улучшение было оправдано, а не технологии, ради технологий.
В примерах некоторые аналоги на абсолютах до сих пор нормально выглядят. Мы всегда берём те свойства которые сейчас актуальны и строим из них опять те же блоки, которые верстаем из года в год и говорим, вот теперь правильно. Но со стороны выглядет только как позёрство, не более.