Привет, Хабр. Я продолжаю рассказывать про неизвестные широкому кругу разработчиков CSS-фишки. Я отбираю их так, чтобы они были полезны в разного рода проектах. Неважно, верстаете ли вы сайт для малого бизнеса или создаёте супермодное React-приложение. Они поддерживаются большинством браузеров. Отдельно отмечу, что я не считаю IE11 современным браузером. По этой причине я не учитывал его.


Сегодня мы рассмотрим:

  • возможность задать несколько фонов с помощью свойства background;
  • свойство display, которое позволяет сделать так, что свойства элемента будут влиять через потомка;
  • как заставить псевдо-элемент nth-child выбрать элементы без привязки к позиции;
  • где будет находиться элемент с position: absolute, если для него заданы свойства grid-column и grid-row.

Больше не буду затягивать. Давайте посмотрим, что я вам подготовил.


▍ Множество фонов для свойства background


Вёрстка текста поверх изображения — обычная задача верстальщика. В ней встречается маска. По задумке дизайнеров, она мешает тексту слиться с фоном. Я создавал её с использованием псевдо-элемента.


<body>
  <div class="hero">
    <span class="hero__msg">Hey! I'm Stas Melnikov</span>
  </div>
</body>

.hero {
  /* здесь стили для позиционирования текста */

  min-height: 100dvh;	
  position: relative;
  isolation: isolate;
	
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;	
  background-image: url("hero.webp");
}

.hero::before {
  content: "";
  inset: 0;
  position: absolute;
  background-color: rgba(15, 63, 122, 0.5);
  z-index: -1;
}

Так я делал достаточно долго. Пока однажды коллега не сказал мне, что псевдо-элемент лишний. Я сначала подумал, что он ошибается, но на всякий случай спросил: «Почему?». Он сказал, что достаточно использовать слои для свойства background. И тут у меня перед глазами всплыл момент из 2013 года.


На обеде у меня была привычка читать про технологии. Как раз был перерыв на обед. Сижу и читаю новость, что теперь можно задавать несколько фоновых изображений. Свойство background стало поддерживать слои.


Очухавшись, я попросил Влада показать его версию кода. Открыв редактор, он удалил псевдо-элемент ::before и добавил функцию linear-gradient() для свойства background-image.


.hero {
  /* здесь стили для позиционирования текста */

  min-height: 100dvh;

  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;	
  background-image: linear-gradient(to top, rgba(15, 63, 122, 0.5), rgba(15, 63, 122, 0.5)), url("hero.webp");
}


Слева — вариант свёрстанный первым способом, а справа — вторым.


Что такое слои для свойства background? Очень давно свойство задавало фоновое изображение или цвет элемента. С появлением слоёв стало можно задавать множество фонов, которые располагаются друг на друге. Последний указанный фон является самым последним, а первый — самый верхний.


В моём примере url("hero.webp") — самый нижний слой, на который накладывается фон с linear-gradient(). В этом решении также интересно, почему используется градиент. Дело в том, что для свойства background-image мы не можем задать однородный цвет. По этой причине Влад использует хак с градиентом, который «изменяется» от одного цвета к такому же цвету.


display: contents «позволяет» пропустить элемент


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


Для начала создам разметку.


<body>
  <div class="intro">
    <div class="intro__container">
      <a href="cv.pdf" class="intro__download">download</a>
    </div>
  </div>
</body>

Поскольку по умолчанию у элемента <a> установлено display: inline, свойство width рассчитывается в зависимости от контента. Мне же требуется сделать так, чтобы оно было эквивалентно свойству width родительского элемента. Первым решением будет использовать display: block для элемента .intro__download.


.intro {
  display: grid;
}

.intro__download {
  /* здесь декоративные стили */

  display: block;
}

Этот код решает мою задачу, но, к сожалению, в своей задаче я не мог его использовать. У меня в проекте множество компонентов. Получилось так, что на ссылке уже было установлено свойство display со значением inline-grid. Изменять его было нельзя. Можно было добавить ещё один класс к элементам .intro__download и в нём установить display: grid.


Но меня смущало, что пришлось бы переопределять значение. Хотелось использовать возможности уже установленного display: grid. Я стал думать дальше, и мне пришло другое решение.


Поскольку у элемента с классом .intro используется display: grid, то свойство width прямого потомка, т. е. элемента с классом .intro__container, будет соответствовать свойству width родительского элемента. А это то, что я хотел сделать для ссылки. Осталось как-то сделать её прямым потомком. Тут я вспомнил про значение contents.


Оно позволяет скрыть элемент так, что свойства родителя будут влиять на его дочерние элементы. То, что нужно в моей задаче.


Осталось добавить его к элементу с классом .intro__container.


.intro {
  display: grid;
}

.intro__container {
  display: contents;
}

.intro__container {
  /* здесь декоративные стили */

  display: inline-grid;
}



Всё! Работает как надо. А главное, не надо добавлять новые классы. Люблю я лаконичность.


Кстати, если поведение элемента с display: grid вызвало у вас вопросы, то у меня есть кое-что. Я рассказал про такие нюансы в отдельной статье. Дальше, я думаю, вы разберётесь.


▍ Можно выбрать конкретные элементы без учёта его позиции


У меня к вам вопрос. Как браузеры обработают следующий код:


:nth-child(2 of .highlight) {
  color: red;
}

Удивил?! Да, я старался. Это новое расширение of S для псевдо-класса :nth-child. Оно позволяет выбрать определённые элементы из списка селекторов без привязки к номеру в DOM. Сейчас покажу, что я имею в виду.


Для нашего примера с классом .highlight я создам следующую разметку:


<body>
  <div class="container">
    <span>1</span>
    <span class="highlight">2</span>
    <span>3</span>
    <span>4</span>
    <span class="highlight">5</span>
    <span class="highlight">6</span>
  </div>
</body> 


Браузеры применят стили ко второму элементу с классом .highlight, который является пятым элементом по порядку! Другими словами, при поиске элемента не важен номер его позиции в DOM. Это главное отличие от привычного синтаксиса псевдо-класса :nth-child.


Вместо S мы можем использовать любой селектор. Например, так можно отобрать первые три элемента <span> с классом important.


.container :nth-child(-n+3 of span.important) {
  color: red;
}

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


<body>
  <div class="container">
    <span class="important">1</span>
    <span>2</span>
    <span>3</span>
    <span class="important">4</span>
    <span>5</span>
    <span class="important">6</span>
  </div>
</body>


Мне лично всегда не хватало такой возможности. Вспоминаю типичную сетку из карточек товаров. И где-то в середине надо было вставить блок с рекламой, который сбивал порядок элементов. Вот и приходилось плясать с бубнами. Так что синтаксис of — весьма полезная штука.


▍ Элемент с position: absolute не всегда располагается относительно границ родительского элемента с position: relative


Я для тренировки люблю проходить собеседования — иногда это даже полезно. Встречаются интересные вопросы, которые помогают узнать что-то новое. Как раз недавно так и произошло.


Сначала мне показали фрагмент кода, где был абсолютно спозиционированный элемент внутри грид-контейнера:


.container {
  box-sizing: border-box;
  min-height: 100dvh;
  border: 1px solid currentColor;

  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-template-rows: repeat(4, 1fr);

  position: relative;
}

.container::before {
  content: "";
  width: 5rem;
  height: 5rem;
  background-color: darkolivegreen;

  position: absolute;
  top: 0;
  left: 0;
}

Меня спросили: «Где будет располагаться псевдо-элемент ::before?». Я с уверенностью ответил, что в левом верхнем углу. Это был правильный ответ.



А дальше интервьюер добавил для псевдо-элемента ::before свойства grid-column и grid-row.


.container {
  box-sizing: border-box;
  min-height: 100dvh;
  border: 1px solid currentColor;

  display: grid;
  grid-template-columns: repeat(6, 1fr);
  grid-template-rows: repeat(4, 1fr);

  position: relative;
}

.container::before {
  content: "";
  width: 5rem;
  height: 5rem;
  background-color: darkolivegreen;

  grid-column: 2 / span 2;
  grid-row: 2 / span 2;

  position: absolute;
  top: 0;
  left: 0;
}

Дальше он спросил: «Изменится ли позиция псевдо-элемента ::before?». Этот вопрос меня сбил с толку. Я думал: «Так, position: absolute выдёргивает элемент из контекста, поэтому свойства grid-column и grid-row не сработают». В итоге я ответил, что изменений не будет. Это был неправильный ответ. Оказывается, что позиция псевдо-элемента ::before изменится .


Свойства grid-column и grid-row определяют позицию дочернего элемента. А как это происходит? На деле мы задаём координаты прямоугольника, в который помещается элемент. Он же используется для отсчёта позиции элементов с position: absolute. По этой причине псевдо-элемент ::before отобразился в левом верхнем углу прямоугольника, созданного строками grid-column: 2 / span 2 и grid-row: 2 / span 2, а не родительского элемента.



▍ Заключение


Давайте подведём итог. Сегодня мы можем с помощью CSS:

  • создавать маски для изображений без дополнительных элементов;
  • пропустить ближайшего потомка с помощью display: contents;
  • с помощью синтаксиса of применять стили к группе элементов без учёта их позиции в DOM;
  • расположить элемент с position: absolute относительно определённой области родительского элемента.

Оставляю ссылки на все выпуски:

Также, пожалуйста, напишите в комментариях, какие CSS-фишки вы используете, о которых другие могут не знать. Буду ждать их. Спасибо за чтение!


P.S. Помогаю больше узнать про CSS в своём ТГ канале CSS isn't magic. Присоединяйтесь. Ссылка в профиле.


Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (11)


  1. Spaceoddity
    14.05.2024 14:31
    +2

    Ну, честно говоря, так себе...

    Несколько изображений для бэкграунда - уже довольно старая фича. Думается мне, что с ней сталкивался любой, кто плотно работал с градиентами.

    display: contents, я лично уже не меньше года использую - сильно помогает привести в соответствие мобильную и десктопную вёрстку (скрывая ненужные обертки).

    Давайте тогда и pointer-events в очередной раз вспомним ;)


  1. IvanG
    14.05.2024 14:31

    Delete


  1. bubn0ff
    14.05.2024 14:31

    Узнал новое для себя, значит статья полезна.


  1. danyatanov
    14.05.2024 14:31

    Использовать backround для фонового изображение через css - это моветон. У нас в компании вообще это под запретом. Только тэг picture и лейзи-лоадинг, к сожалению. Да, можно использовать image-set, но функционал у него не такой богатый, да и поддержка браузеров 84 процента может не всех устроить.


    1. Spaceoddity
      14.05.2024 14:31
      +4

      Мой вам совет - бегите из этой компании))

      Это же надо такую дичь придумать - для бэкграунда элемента использовать дополнительную сущность в html-разметке... Я уж молчу про семантику, но как вы это юзаете? Каждому элементу с бэкграундом - position: relative, а затем позиционированный picture?

      Моветон... У меня просто нет слов))


      1. Ione1991
        14.05.2024 14:31

        Видимо изображения должны редактироваться из админки, если это не простенький статичный сайт-визитка.


        1. StudioWest
          14.05.2024 14:31

          так используйте var для url бекграунда в js - все меняется !


    1. space_bro
      14.05.2024 14:31

      С чем связан такой запрет? background ведь создан для фонового изображения


  1. Beholder
    14.05.2024 14:31

    Как ещё может быть полезен display: contents: если вам неохота или сложно генерировать уникальные идентификаторы для пар элементов <label for="login">Login</label><input id="login" .../> , то можно же вкладывать поле внутрь метки: <label>Login <input .../></label> , но так поломается разметка сеткой, тогда вот для label можно и указать данное свойство.


    1. melnik909 Автор
      14.05.2024 14:31

      Тогда ваш label потеряется для скринридера, а поле будет без имени


      1. Beholder
        14.05.2024 14:31

        Ну есть что-то такое, но это не по спецификации, а баги каких-либо браузеров, когда-нибудь исправят. Можно попробовать добавить span и прописать aria- атрибуты.