Черт тебя возьми, CSS

f53e73c4d6027ccf5aafbc26ae3ea5dd.png

У CSS много моментов, которые сбивают столку. Разработчики плюются от него. А мне нравится CSS, несмотря на мои потраченные нервы. Подумав, как помочь другим меньше мучаться, я собрал ряд неочевидных моментов. Они сбивали с толку меня и моих знакомых. Надеюсь, вам будет полезно.


▍ Свойство display

Однажды на собеседовании мне задали вопрос: «Какое значение свойства display у элемента с классом child будет во вкладке Computed в DevTools?»

Я дочерний элемент внутри родителя с display: flex
.parent {
  display: flex;
}

.child {
  display: inline-grid; /* какое итоговое значение свойства display будет здесь? */
}

Если вы думаете, что inline-grid, то вы попались на удочку, как и я. Нет, это не правильный ответ. Правильный ответ — grid.

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

Если используются значения inline, inline-block, inline-flex, inline-grid или inline-table, то они трансформируются. Значение inline и inline-block в block, inline-flex в flex, inline-grid в grid и inline-table в table.

По этой причине мы увидим grid во вкладке Computed в DevTools.

.parent {
  display: flex;
}

.child {
  display: inline-grid; /* Здесь будет значение grid, а не inline-grid */
}

Также мне сказали, что точно такое же поведение есть при использовании значений grid, inline-grid и inline-flex:

Я дочерний элемент внутри родителя с display: grid
Я дочерний элемент внутри родителя с display: inline-grid
Я дочерний элемент внутри родителя с display: inline-flex
.parent-grid {
  display: grid;
}

.parent-inline-grid {
  display: inline-grid;
}

.parent-inline-flex {
  display: inline-flex;
}

.child {
  display: inline-flex; /* Во всех случаях здесь будет значение flex, а не inline-flex */
}

После собеседования, я попытался найти объяснение этому процессу. Ответ был в стандартах CSS Flexible Box Layout Module Level 1 и CSS Grid Layout Module Level 1.

В них говорится, что значение свойства display у дочерних элементов внутри флекс-контейнера и грид-контейнера становится blockified, т.е inline-* значение трансформируется, как мне сказали на собеседовании.

Получается, я долгое время делал ошибку, определяв display: block для псевдо-элемента ::before или ::after, находящегося внутри элемента с display: flex:

.parent {
  display: flex
}

.parent::before {
  content: "";
  display: block;
 
  width: 1rem;
  height: 1rem;
  background-color: mediumspringgreen;
}

По умолчанию для псевдо-элементов ::before и ::after значение свойства display установлено, как inline. Для таких элементов браузеры не могут задать размеры с помощью свойств width и height, поэтому я объявлял display: block.

Во вкладке Compted в DevTools я видел display: block, но это не тот display: block, которые я устанавливал. Браузеры сами добавили display: block.

Удалив его из кода, я убедился, что изменений нет.

.parent {
  display: flex
}

.parent::before {
  content: "";  
  width: 1rem;
  height: 1rem;
  background-color: mediumspringgreen;
}

На этом я не остановился. Начав гуглить про свойство display, я увидел, что есть ещё ситуация, когда браузеры мухлюют с ним. Давайте рассмотрим следующий фрагмент кода:

.parent::before {
  content: "";
  display: block;
  background-color: mediumspringgreen;
 
  position: absolute;
  inset: 0;
}

Догадались, какое свойство можно удалить? Конечно, свойство display. Браузеры делают проверку значения свойства display у элемента, если при объявлении свойства position используется значение absolute или fixed.

Согласно таблице описанной в стандарте значение inline, inline-block или любое из table-*трансформируется в значение block, а inline-table в table. Значений inline-flex и inline-grid нет в стандарте, но я проверил их вручную. Результат — они изменяются на flex и grid.

Вернусь к коду и удалю display: block.

.parent::before {
  content: "";
  background-color: mediumspringgreen;

  position: absolute;
  inset: 0;
}


▍ Математическая функция calc()

У меня есть привычка не писать единицы измерения вместе с 0. Однажды мне нужно было использовать математическую функцию calc() для вычисления ширины элемента следующим образом:

:root {
  --default-gap: 1rem;
}

.container {
  width: calc(0 + var(--default-gap));
}

К моему удивлению — этот код не работал. Открыв стандарт CSS Values and Units Module Level 3, я нашёл уточнение. При использовании математической функции calc() для свойства со значением типа , 0 без единицы измерения не поддерживается.

Здесь, как мне кажется, нужно пояснить. В функции можно использовать несколько типов значений:


  •  — число, состоящие из одной или несколько цифр в диапазоне от 0 до 9. Перед первой цифрой может быть установлен знак — или +;
  •  — число как , но может быть записано с помощью экспоненциальной формы записи;
  •  — тип для измерения дистанции, который является числом с единицей измерения.

Вернусь кпримеру. Браузеры не могут понять, что 0 без единицы измерения используется как тип . Нужно исправить:

:root {
  --default-gap: 1rem;
}

.container {
  width: calc(0px + var(--default-gap));
}

Ещё в стандарте есть неочевидные требования, которые следует помнить при работе с calc(). Так, при сложении и вычитании оба аргумента должны быть либо одного типа, либо один из аргументов должен быть типом , а другой .

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

/* правильный пример */

.container {
  width: calc(1000px + 3rem);
  opacity: calc(0.5 + 0.35);
  z-index: calc(1E2 + 1);
}

.container {
  width: calc(1000px - 3rem);
  opacity: calc(0.5 - 0.35);
  z-index: calc(1E2 - 1);
}

/* неправильный пример */

.container {
  width: calc(1000px + 32);
  height: calc(1000px + 1E1);
}

.container {
  width: calc(1000px - 32);
  height: calc(1000px - 1E1);
}

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

/* правильный пример */

.container {
  width: calc(10px * 1E1);
  height: calc(100px * 2);
  opacity: calc(0.25 * 2);
  z-index: calc(1E2 * 1E1);
}

.container {
  width: calc(900px / 3E1);
  height: calc(100px / 2);
  opacity: calc(1 / 2);
  z-index: calc(1E2 / 1E1);
}

/* неправильный пример */

.container {
  width: calc(1000px * 10px);
}

.container {
  width: calc(60000px / 6px);
}


▍ Свойство min-width

Однажды мой ученик сделал демку и задал мне вопрос: «Почему в devTools у элемента с классом container у свойства min-width установлено значение 0, а у элемента с классом content — auto?


  
здесь какой-то контент
.container {
  display: grid;
}

.content {
  box-sizing: border-box;
  max-width: 100rem;
  padding-inline: 1rem;
}

В стандарте CSS Box Sizing Module Level 3 сказано, что у свойства min-width есть значение по умолчанию auto. Так почему мы видим 0? В разделе описания допустимых значений для свойства есть пояснение.

Браузеры преобразуют значение auto в 0, если для родительского элемента определено свойство display со значениями block, inline, inline-block, table или со всеми table-* значениями.

В примере элемент div с классом container находится внутри элемента body, у которого по умолчанию установлено display: block. По этой причине браузеры неявно устанавливают значение 0 для свойства min-width. А элемент с классом content находится внутри элемента container c display: grid, поэтому в devTools мы увидим уже min-width: auto.


▍ Свойство position и значение fixed

Вы знали, что есть несколько случаев, когда элемент с position: fixed не будет зафиксированным на экране, а его размеры не будут рассчитаны от вьюпорта? Например, как в следующем коде:

.demo {
  width: 300px;
  height: 200px;
  background-color: mediumspringgreen;
  transform: translate3d(0, 0, 0);
}
 
.demo::before {
  content: "";
  width: 50%;
  height: 50%;
  background-color: lightgoldenrodyellow;

  position: fixed;
  top: 1rem;  
  right: 1rem;
}

Для понимания причины такого поведения нужно вспомнить, что у каждого элемента есть родительская область относительно, которой рассчитываются размеры и позиция дочернего элемента. В стандарте CSS Display Module Level 3 ей дали название containing block. У элемента с position: fixed такой областью обычно является вьюпорт.

А теперь самое интересное. Существует ряд свойств, которые влияют на определение такой области. В частности свойство transform. В стандарте CSS Transforms Module Level 1 сказано, что если для элемента указано свойство transform со значением отличного от none, то все его дочерние элементы с position: fixed будут использовать его, как containing block. Таким образом — они перестают быть «зафиксированными».

Именно это произошло в моём примере. Определив transform: translate3d(0, 0, 0) для элемента .demo, я изменил область расчётов позиции и размеров у псевдо-элемента .demo::before.

.demo {
  width: 300px;
  height: 200px;
  background-color: mediumspringgreen;
  transform: translate3d(0, 0, 0);
}
 
.demo::before {
  content: "";
  width: 50%; /* здесь итоговое значение будет 150px, а не половина ширины вьюпорта */
  height: 50%; /* здесь итоговое значение будет 100px, а не половина высоты вьюпорта */
  background-color: lightgoldenrodyellow;

  position: fixed;
  top: 1rem;  
  right: 1rem;
}

Кроме свойства transform также влияют:


  • свойства perspective, backdrop-filter и filter со всеми значениями, кроме none;
  • свойство contain со значениями layout, paint, strict и content;
  • свойство will-change со значениями transform, filter и backdrop-filter.


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

А какие у вас были случаи, когда CSS сбил вас с толку? Делитесь в комментариях. Наберём на новую статью! Спасибо.


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

© Habrahabr.ru