
У CSS много моментов, которые сбивают с толку. Разработчики плюются от него. А мне нравится CSS, несмотря на мои потраченные нервы. Подумав, как помочь другим меньше мучаться, я собрал ряд неочевидных моментов. Они сбивали с толку меня и моих знакомых. Надеюсь, вам будет полезно.
▍ Свойство display
Однажды на собеседовании мне задали вопрос: «Какое значение свойства display у элемента с классом child будет во вкладке Computed в DevTools?»
<div class="parent"> <span class="child">Я дочерний элемент внутри родителя с display: flex</span> </div>
.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:
<div class="parent-grid"> <span class="child">Я дочерний элемент внутри родителя с display: grid</span> </div> <div class="parent-inline-grid"> <span class="child">Я дочерний элемент внутри родителя с display: inline-grid</span> </div> <div class="parent-inline-flex"> <span class="child">Я дочерний элемент внутри родителя с display: inline-flex</span> </div>
.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.
Во вкладке Computed в 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() для свойства со значением типа <length>, 0 без единицы измерения не поддерживается.
Здесь, как мне кажется, нужно пояснить. В функции можно использовать несколько типов значений:
<integer>— число, состоящие из одной или несколько цифр в диапазоне от 0 до 9. Перед первой цифрой может быть установлен знак — или +;<number>— число как<integer>, но может быть записано с помощью экспоненциальной формы записи;<length>— тип для измерения дистанции, который является числом с единицей измерения.
Вернусь к примеру. Браузеры не могут понять, что 0 без единицы измерения используется как тип <length>. Нужно исправить:
:root { --default-gap: 1rem; } .container { width: calc(0px + var(--default-gap)); }
Ещё в стандарте есть неочевидные требования, которые следует помнить при работе с calc(). Так, при сложении и вычитании оба аргумента должны быть либо одного типа, либо один из аргументов должен быть типом <number>, а другой <integer>.
Соответственно, если используется один аргумент типом <length>, а другой <integer> или <number>, то будет ошибка.
/* правильный пример */ .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); }
При умножении и делении один из аргументов обязательно должен быть типом <integer> или <number>. Если оба аргумента являются типом <length>, то будет ошибка. При делении, если правый аргумент является типом <integer>, то браузеры трансформируют его в тип <number>.
/* правильный пример */ .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?
<body> <div class="container"> <div class="content">здесь какой-то контент</div> </div> </body>
.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 сбил вас с толку? Делитесь в комментариях. Наберём на новую статью! Спасибо.
P.S. Также посмотрите второй выпуск.
P.S.S. Помогаю больше узнать про CSS. Пишите мне в ТГ. Ссылка в профиле.
Выиграй телескоп и другие призы в космическом квизе от RUVDS. Поехали? 🚀

