Черт тебя возьми, CSS
У 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. Поехали?