В 2020 году я поделился списком моих любимых вопросов о CSS, который стал довольно популярным, судя по просмотрам. Спустя 3 года CSS изменился, и я решил дополнить список, добавив вопросы про гриды, пользовательские свойства (CSS-переменные), новые селекторы и свойства.

Работая над вопросами, мне хотелось помочь вам в изучении новых возможностей CSS и тех моментов, которые многие разработчики упускают, судя по моей практике. Также вы можете использовать их, если проводите интервью. Я буду только рад этому. А теперь давайте начнём.

▍ Какая специфичность следующего правила?


:is(#container, .content, main) {
  color: red;
}

Ответ
Принцип работы псевдокласса :is() заключается в том, что браузеры выбирают наиболее специфичный селектор из переданных. В нашем случае передан селектор #container, у которого специфичность 0,1,0,0. Она же будет использоваться для всего правила.

▍ Верно ли, что браузеры вычислят значение red для свойства color?


<body>
  <span id="label" class="label">текст элемента</span>
</body>



.label {
  color: red;
}

:where(#label) {
  color: blue;
}

Ответ
Верно. Поскольку псевдокласс :where() имеет специфичность 0, то в нашем примере более специфичным будет правило с селектором .label. Соответственно, браузеры возьмут значение для свойства color из него.

▍ Перепишите следующий код так, чтобы свойства применялись при клике на кнопку, но не применялись, когда на неё сфокусировались при помощи клавиатуры.


.button {
  outline: none;
  color: red;
}

Ответ
Для определения способа фокусировки на кнопке я буду использовать псевдокласс :focus-visible, который срабатывает, когда пользователь сфокусировался на интерактивном элементе при помощи клавиатуры.

В задаче требуется не применять свойства, поэтому нужно использовать инверсию, т. е псевдокласс :not(), а также добавить псевдокласс :focus, чтобы стили срабатывали после клика по кнопке.

.button:focus:not(:focus-visible) {
  outline: none;
  color: red;
}


▍ Какое вычисленное значение для свойства display у псевдоэлементов ::before и ::after?



.parent {
  display: inline-grid;
}

.parent::before {
  content: "";
  display: inline;	
}

.parent::after {
  content: "";
  display: flex;
}

Ответ
После объявления значений grid или inline-grid для элемента браузеры сделают проверку значения для свойства display его дочерних элементов. Если у них будет значение inline, inline-block, inline-flex, inline-grid или inline-table, то оно трансформируется в блочный аналог, а именно inline, inline-block в block, inline-flex в flex, inline-grid в grid и inline-table в table.

В нашем примере у псевдоэлемента ::before установлено значение inline, поэтому оно преобразуется в block, а для псевдоэлемента ::after значение flex будет сохранено.

.parent {
  display: inline-grid;
}

.parent::before {
  content: "";
  display: inline; /* здесь будет display: block */		
}

.parent::after {
  content: "";
  display: flex; /* здесь будет display: flex */
}


▍ Как по умолчанию отображаются дочерние элементы внутри родительского с display: grid или display: inline-grid?


Ответ
После объявления display: grid или display: inline-grid для родителя его дочерние элементы начинают отображаться в столбец.

▍ Чему равняется вычисленное значение для свойства width и height у элементов .child?


<body>
  <div class="parent">
    <div class="child">элемент №1</div>
    <div class="child">элемент №2</div>
  </div>
</body>

.parent {
  display: grid;
  width: 1000px;
  height: 400px;
}

Ответ
В нашем примере используется display: grid. В этом случае свойство width будет эквивалентно свойству width родительского элемента, т.е будет 1000px. В свою очередь, вычисление свойства height зависит от количества элементов.

Если внутри грид-контейнера один элемент, то свойство height будет таким же, как у родителя. Если несколько элементов, то свойство height распределяется между элементами. В примере у всех дочерних элементов не объявлено свойство height, поэтому у них одинаковая высота, следовательно, нужно разделить 400px на 2, и мы получим 200px.

▍ Верно ли, что внешние отступы у первого и последнего элемента p выходят за границы родительского элемента?


body {
  display: grid;
}

p {
  margin-top: 1em;
  margin-bottom: 1em;
}

Ответ
После объявления display: grid внешние отступы у первого и последнего элемента перестают выходить за пределы родительского элемента. Соответственно, правильный ответ — неверно.

▍ Если добавить display: grid к элементу, будут ли схлопываться внешние отступы у его дочерних элементов?


Ответ
Внутри грид-контейнера внешние отступы между элементами суммируются, а не схлопываются.

▍ Псевдоэлемент ::before располагается по центру по горизонтали и по вертикали. Да или нет?


.box {
  display: inline-grid;
  width: 200px;
  height: 200px;
}

.box::before {
  content: "";
  width: 15px;
  height: 15px;
  margin: auto;
}

Ответ
Да, псевдоэлемент ::before располагается по центру по горизонтали и по вертикали, потому что внутри грид-контейнера автоматические внешние отступы распределяются равномерно по обеим осям.

▍ В следующем примере для свойства padding будет установлено значение 0. Правда или ложь?


:root {    
  --padding-vertical-start: 30px;
  --padding-horizontal-end: 40px;
  --padding-vertical-end: 50px;
}

.box {
  padding: var(--padding-vertical-start) 
	       var(--padding-horizontal-end) 
	       var(--padding-vertical-end) 
	       var(--padding-horizontal-start);
}

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

В примере со свойством padding из-за того, что не хватает одного значения, браузеры не знают, к какой стороне относят переданные значения 30px, 40px и 50px, поэтому вычисленное значение для всех сторон будет 0, соответственно, правильный ответ — правда.

▍ Какое вычисленное значение будет у свойства font-size и background-color у элемента p?


body {
  --font-size: purple;
  --background-color: 20px;
	
  font-size: 10px;
  background-color: green;
}

p {
  --font-size: inherit;
  --background-color: inherit;
	
  font-size: var(--font-size, 50px);
  background-color: var(--background-color, red);
}

Ответ
При использовании ключевого слова inherit для пользовательских свойств нужно помнить, что они наследуют значение только между собой, и поэтому пользовательские свойства --font-size и --background-color унаследуют значения purple и 20px.

body {
  --font-size: purple;
  --background-color: 20px;
	
  font-size: 10px;
  background-color: green;
}

p {
  --font-size: inherit; /* здесь значение purple */
  --background-color: inherit; /* здесь значение 20px */
	
  font-size: var(--font-size, 50px);
  background-color: var(--background-color, red);
}

Далее эти значения используются для свойств font-size и background-color, поэтому значения по умолчанию 50px и red будут проигнорированы. После этого браузеры должны вместо функции var() подставить значение пользовательского свойства. Перед тем как это сделать, они проверят, является ли значение корректным для свойства, для которого происходит замена.

В нашем случае значения некорректны, потому что значение purple должно быть подставлено для свойства font-size, а значение 20px для свойства background-color. Но ошибки здесь не будет! В этом случае есть алгоритм, согласно которому браузеры подставят корректное значение.

Для этого они анализируют свойство, для значения которого происходит замена. Если оно наследуемое, то значение будет вычислено в результате наследования, если нет, то будет использоваться начальное значение (initial).

В нашем примере свойство font-size наследуемое, поэтому значение для элемента p будет 10px, которое наследуется от свойства font-size элемента body, а свойство background-color ненаследуемое, поэтому будет использоваться начальное значение, а именно transparent.

body {
  --font-size: purple;
  --background-color: 20px;
	
  font-size: 10px;
  background-color: green;
}

p {
  --font-size: inherit; 
  --background-color: inherit; 
	
  font-size: var(--font-size, 50px); /* здесь получается font-size: 10px */
  background-color: var(--background-color, red); /* здесь получается background-color: transparent */
}


▍ Почему для элемента p браузеры вычислят значение green для свойства background-color?


body {
  background-color: green;
}

p {
  --background-color: inherit;
  background-color: var(--background-color, inherit);
}

Ответ
Поскольку для элемента body не определено пользовательское свойство --background-color, то наследовать значение с помощью ключевого слова inherit неоткуда. Так что значение для пользовательского свойства --background-color будет не определено.

Соответственно, браузеры подставят значение по умолчанию inherit, а с помощью него передадут значение green по алгоритму наследования.

▍ Каким свойством можно сократить количество определённых свойств для псевдоэлемента ::before до трёх?


.parent {
  position: relative;
}

.parent::before {
  content: "";
  width: 100%;
  height: 100%;
  
  position: absolute;
  top: 0;
  left: 0;
}

Ответ
Разобьём задачу на этапы. Поскольку используется position: absolute, то можно альтернативно установить размеры с помощью свойств top, right, bottom и left.

.parent {
  position: relative;
}

.parent::before {
  content: "";
  
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

Теперь сократим их в одно свойство inset.

.parent {
  position: relative;
}

.parent::before {
  content: ""; 
  position: absolute;
  inset: 0;
}



▍ Перепишите следующий код так, чтобы определение значений для свойств margin и padding было без использования 0


.box {
  margin: 0 20px 0 40px;
  padding: 30px 0 15px;
}

Ответ
Здесь мы можем использовать логические свойства margin-block, margin-inline, padding-block и padding-inline. Важно помнить, что первым значением устанавливается значение сначала (сверху и слева), а вторым — значение с конца (снизу и справа).

.box {
  margin-inline: 40px 20px;
  padding-block: 30px 15px;
}



▍ Как с помощью свойства gap можно сократить следующий код до одного правила?


ul {
  display: flex;
}

li:not(:last-child) {
  margin-right: 15px;
}

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

ul {
  display: flex;
  gap: 15px;
}


▍ Как с помощью свойства isolation расположить псевдоэлемент ::after позади текста, чтобы он не перекрывался фоном элемента .parent?


<body>
  <div class="parent">
    <div class="container">
      <span>текст элемента</span>
    </div>
  </div>
</body>

.parent {
  background-color: purple;
}

.container {
  position: relative;
}

.container::after {
  content: "";
  background-color: green;
  
  position: absolute;
  inset: 0;
  z-index: -1;
}

Ответ
Когда используется отрицательное значение для свойства z-index, то мы должны помнить, относительно какого контекста наложения (stacking context) будет расчёт положения. Поскольку в коде не создаётся новый контекст, то будет использоваться корневой, то есть элемент html. Поэтому псевдоэлемент ::after с отрицательным z-index будет позади элемента .parent.

Чтобы отобразить его позади текста элемента .container, нужно сделать этот элемент новым контекстом с помощью свойства isolation, для которого задать значение isolate.

.parent {
  background-color: purple;
}

.container {
  position: relative;
  isolation: isolate;
}

.container::after {
  content: "";
  background-color: green;
  
  position: absolute;
  inset: 0;
  z-index: -1;
}


▍ Вместо заключения


Мне интересно прочитать, какие темы о CSS, по вашему мнению, я упустил. Или, возможно, у вас есть свой список любимых вопросов. Так что, пожалуйста, делитесь. Я нацелен написать третью версию через 3 года! Ваша помощь будет очень кстати. Спасибо.

Выиграй телескоп и другие призы в космическом квизе от RUVDS. Поехали? ????

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


  1. bubn0ff
    11.07.2023 14:45
    +5

    Заковыристые вопросы, однако. ???? Но оно того стоит! Познавательная статья, почерпнул для себя новые свойства. Автору благодарность. ????


  1. Myclass
    11.07.2023 14:45
    +7

    Спасибо за вопросы. Но для себя подумал - css изначально создавался как стильный инструмент над html и включал в себя, что этим будут заниматься дизайнеры, а не программисты. В этом была своего суть разделения задач. Но сейчас css такой мощный, но при этом и сложный, что 'просто' креативным челам он неподступен. Только спецам, которые могут и хотят погружаться по полной.


  1. DmitryOlkhovoi
    11.07.2023 14:45
    +18

    Более 10 лет нечем кроме как разработкой веб сайтов не занимаюсь, причем проекты крупные, дорогие с разными клиентами а-ля Apple. От визуальных редакторов лендосов до систем bowtie на нефтяных вышках. Разумеется своих бутстрапов в сторибуках понаписано.

    И по правде говоря, я наверное ни на один вопрос, твердо не отвечу. Да кому я вру, я ваще не шарю все это, тем более :is и :where мне кажется впервые вижу.
    Возможно я тупой, но проекты делаются, задачи решаются и без ответов на эти вопросы, тем более на собесе или что бы я у кого-то это спросил лол


    1. Pab10
      11.07.2023 14:45
      +8

      Согласен. Бизнесу все равно как это сделано, если оно удовлетворяет требованиям. Поэтому зачастую проще использовать устаревшие технологии, которые с большей вероятностью будут работать у большинства пользователей. И как показывает практика этих устаревших технологий хватает с головой :)

      Заголовок статьи надо переименовать в "Мои любимые вопросы о CSS для верстальщиков-экспертов с ответами." Тут реально надо просто иметь спеку в голове.

      Для общего развития - шикарная статья, спасибо.


    1. Myclass
      11.07.2023 14:45
      +1

      Ну это так было и будет. Кому-то кроме стамески и молотка ничего не надо, кому-то бумаги и ножниц - чтобы чудеса творить. А кому-то и 3D принтера уже мало.


    1. Alexander_Tamirov
      11.07.2023 14:45

      То же самое.
      Да потому что Caniuse покажет, что году в 21 только стали эти свойства поддерживаться.
      Все пользователи по команде браузеры обновили что ли? :)
      А нам нужно чтоб работало без "сюрпризов".
      P. S. Статья интересная, спасибо!


  1. sfi0zy
    11.07.2023 14:45
    +12

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

    Чему равняется вычисленное значение для свойства width и height у элементов .child?

    Очевидно оно зависит не только от самой grid-сетки, но и от содержимого этих самых child. Сделайте им размер шрифта побольше и удивитесь несоответствию результата вашему ответу как по высоте, так и по ширине.

    Каким свойством можно сократить количество определённых свойств для псевдоэлемента ::before до трёх?

    Замена установленных height и width на четверку top, right, bottom, left, и потом на inset - это, конечно здорово и спортивно, только это меняет логику работы кода. Добавьте, например, margin для ::before, и окажется, что код в вопросе и ответе работает по-разному.

    Перепишите следующий код так, чтобы определение значений для свойств margin и padding было без использования 0

    Свойства margin, margin-block и margin-inline (и их составляющие в виде top, right, left и bottom) имеют разную логику работы. Версии block и inline зависят от свойств writing-mode, direction и text-orientation. Так что замена обычного margin на них может сломать верстку. Код в вопросе и ответе не эквивалентен. То же имеет отношение и к padding.

    Так что вопросы, конечно, хороши, но в теории. А работа есть работа. Я бы не стал такое рекомендовать для вопросов на интервью. Для себя, почитать и восхититься возможностями языка - да, но не для интервью. Штуки вроде :focus-visible гуглятся за минуту при необходимости. За смешивание гридов, флексов и инлайнов на одном элементе нужно на ревью по голове стучать, чтобы коллег не путали неявными приведениями свойств друг к другу. Код долен быть очевидным, однозначно трактуемым с первого взгляда. А вычисленные значения будут зависеть от контекста и мы в подозрительных случаях будем смотреть в DevTools, чтобы видеть весь контекст, а не играть в угадайку.


    1. melnik909 Автор
      11.07.2023 14:45

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

      Да, код работает не в вакууме. Вопросы создаются в вакууме, потому что проверятся/показывается какой-либо аспект-знание. Умение соединять разные аспекты, в частности " Сделайте им размер шрифта побольше", это отдельный навык, который не проверяется вопросами. Я постарался дать примеры кода, который максимально приближенны к практике. Например, свойством inset или margin-inline.

      По поводу логических свойств. Посмотрите видео Kevin Powell или статьи Ahmad Shadeed. Они используют их так, как я написал в вопросах. То, что они могут сломать верстку при определении другого направления текста? Так люди либо это знают, либо узнают и примут решение, что с этим делать. Также существуют разработчики, которые делают проекты для микрокомпаний, у которых мультиязычность не востребована. У них ничего не сломается.

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


      1. sfi0zy
        11.07.2023 14:45
        +6

        По поводу логических свойств. Посмотрите видео Kevin Powell или статьи Ahmad Shadeed. Они используют их так, как я написал в вопросах.

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

        То, что они могут сломать верстку при определении другого направления текста? Так люди либо это знают, либо узнают... Статья про расширение кругозора

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


  1. darkair2
    11.07.2023 14:45
    +11

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


    1. orekh
      11.07.2023 14:45
      +1

      Я бы сказал, что это проблемы дизайна языка CSS. Вот зачем считать специфичность, если порядок применения стилей определялся бы по порядку определения в файле стилей? - это убрало бы треть каверзных вопросов из статьи.


      1. NicolaiCherezov
        11.07.2023 14:45

        это может замечательно сработает для одного файла стилей, но если файлов много и подключаются они не в строго определенном порядке, да еще где-то они инлайново в html вставляются в теге style


        1. orekh
          11.07.2023 14:45

          ...тогда что? Надеюсь, что не открою для вас секрет, но в CSS и сейчас учитывается порядок объявления правил.

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

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

          Чаще всего стили организуются по методологии BEM, целью которой является изоляция стилей по префиксу с названием компонента, а так же полное изничтожение концепции специфичности CSS. Популярность BEM - явный признак того, что специфичность селекторов CSS вредна.


  1. Zam_dev
    11.07.2023 14:45

    (прошу прощения у автора поста)

    Так как вопрос разработчикам Хабра. Дело в том, что времени читать посты, остается в ночное время с телефона, закладок много (черезчур) отложенных на прочтение, но из-за отсутствия ночной (темной) темы, я не могу сдвинуться с места.

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

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