
Привет, Хабр!
CSS часто преподносит сюрпризы, способные запутать даже опытных разработчиков. Я понимаю их раздражение. Тут всё закономерно.
Однако, несмотря на потраченные нервы, мне нравится CSS. Именно поэтому мне хочется, чтобы разработчики тратили меньше времени на борьбу с ним. С этой целью я собрал ряд не самых очевидных моментов, которые в своё время ставили в тупик меня и моих коллег.
Математические выражения и функция calc()
Я не люблю указывать единицы измерения, когда нужно объявить 0. Однажды я столкнулся с тем, что математическая функция calc() не работает в моём любимом стиле.
Для демонстрации давайте объявим свойство padding и рассчитаем его значение с помощью сложения.
:root { --ad-width: 1rem; } body { padding: calc(var(--basis-body-gap, 0) + var(--ad-width)); /* здесь будет calc(0 + 1rem) */ }

Посмотрев в блочную модель, я увидел, что значение не применилось. И никаких ошибок не было. «Очень странно», — подумал я.
Обратившись к стандарту CSS Values and Units Module Level 3, я обнаружил объяснение такого поведения. Оказывается, при использовании функции calc() для свойства, принимающего значения типа <length> , значение 0 без указания единицы измерения не поддерживается.
Возможно, тут стоит уточнить про типы значений. Внутри функции calc() можно использовать:
-
<integer>— целые числа, состоящие из одной или нескольких цифр от 0 до 9, возможно, с предшествующим знаком+или-; -
<number>— числа типа<integer>, но также допускающие экспоненциальную запись; -
<length>— значения, используемые для измерения расстояний, представляющие собой числа с указанием единицы измерения.
Возвращаясь к моему примеру, браузеры не смогли интерпретировать 0 в качестве значения типа <length>. После того, как я добавил единицы измерения rem, код заработал.
:root { --ad-width: 1rem; } body { padding: calc(var(--basis-body-gap, 0px) + var(--ad-width)); /* здесь будет calc(0px + 1rem) */ }

Стандарт также содержит некоторые неочевидные нюансы, которые важно учитывать при работе с функцией calc(). Например, при сложении и вычитании необходимо, чтобы у всех аргументов был одинаковый тип, либо один из аргументов был типом <number> , а другой — <integer>.
/* правильный пример */ .awesome-block { width: calc(1500px + 5rem); opacity: calc(0.75 + 0.15); z-index: calc(1E2 + 5); } .awesome-block { width: calc(1500px - 2rem); opacity: calc(0.45 - 0.15); z-index: calc(1E2 - 1); } /* неправильный пример */ .awesome-block { width: calc(1500px + 35); height: calc(1500px + 1E1); } .awesome-block { width: calc(1500px - 35); height: calc(1500px - 1E1); }
Перейдём теперь к умножению и делению. Здесь нужно помнить, что один из аргументов обязательно должен быть типа <integer> или <number>. Если используется тип <length> для обоих аргументов, то возникнет ошибка.
Дополнительный нюанс есть при делении. Браузеры преобразуют правый аргумент в тип<number>, если он был <integer>.
/* правильный пример */ .awesome-block { width: calc(15px * 1E1); height: calc(150px * 2); opacity: calc(0.25 * 3); z-index: calc(1E2 * 1E1); } .awesome-block { width: calc(800px / 3E1); height: calc(200px / 2); opacity: calc(1 / 2); z-index: calc(1E2 / 1E1); } /* неправильный пример */ .awesome-block { width: calc(1000px * 20px); } .awesome-block { width: calc(6000px / 6px); }
Свойство aspect-ratio
Я очень редко использовал свойство aspect-ratio. Разве только добавлял его к изображениям. По этой причине думал, что у меня не должно с ним возникнуть каких-либо проблем. Но однажды из-за любопытства решил прочитать стандарт CSS Box Sizing Module Level 4 и нашёл для себя много неожиданных вещей.
Для их объяснения мы начнём с термина «предпочтительное соотношение сторон» (preferred aspect ratio). Оно нужно для расчёта автоматических значений размеров элемента. Свойство aspect-ratio устанавливает его.
В качестве примера создадим квадрат.
<body> <div class="awesome-box"></div> </body>
.awesome-box { width: 150px; aspect-ratio: 1; }

Браузер, используя предпочтительное соотношение сторон, рассчитал значение для свойства height, используя значение 1 для свойства aspect-ratio. В результате мы получили желаемый результат.
Но в разметке у элемента нет текста. Добавим его и посмотрим, как изменятся размеры.
<body> <div class="awesome-box"> <span>CSS часто преподносит сюрпризы, способные запутать даже опытных разработчиков. Я понимаю их раздражение. Тут всё закономерно.</span> </div> </body>

Квадрат растянулся. На первый взгляд, это может показаться ошибкой. Однако это не так. Благодаря предпочтительному соотношению сторон, свойство aspect-ratio учитывает различные факторы, влияющие на размеры элемента.
Контент — один из них. Если ему требуется больше места, чем предполагает заданное соотношение сторон, то браузеры нарушат значение, указанное свойством aspect-ratio. Это, как мы увидели, произошло в нашем примере.
Перейдём к следующему случаю. Мы рассмотрим элементы, находящиеся внутри флекс-контейнера.
<body> <div class="awesome-box"> <span>1/1</span> </div> <div class="awesome-box"> <span>1/2</span> </div> </body>
body { display: flex; gap: 1rem; } .awesome-box { width: 150px; aspect-ratio: 1; } .awesome-box:nth-child(2) { aspect-ratio: 1 / 2; }

По умолчанию флекс-элементы стремятся растянуться вдоль дополнительной оси. Если к ним также добавлено свойство aspect-ratio, то мы получим неожиданный результат. Браузер применит одинаковое значение свойства aspect-ratio для всех элементов. Оно будет выбрано как максимальное среди всех объявленных.
В нашем примере у первого флекс-элемента благодаря свойству aspect-ratio со значением 1 размер элемента по дополнительной оси составляет 150px. У второго флекс-элемента используется значение 1/2, что приводит к размеру в 300px. Поскольку 300px больше, чем 150px, браузер решит применить значение 1/2 ко всем флекс-элементам.
Но такой механизм работает до тех пор, пока мы не добавим больше контента.
<body> <div class="awesome-box"> <span>CSS часто преподносит сюрпризы, способные запутать даже опытных разработчиков. Я понимаю их раздражение. Тут всё закономерно.</span> </div> <div class="awesome-box"> <span>1/2</span> </div> </body>

Правда, есть несколько способов сохранить указанные значения. Один из них — это использование свойства overflow со значением auto, если его добавить к элементу, для которого задано свойство aspect-ratio.
Давайте применим это решение к элементу .awesome-box из первого примера.
.awesome-box { width: 150px; aspect-ratio: 1; overflow: auto; }

Внутри флекс-контейнера этот способ тоже работает. Только он возвращает механизм, в результате которого для всех флекс-элементов применится максимальное значение соотношения сторон.
body { display: flex; gap: 1rem; } .awesome-box { width: 150px; aspect-ratio: 1; overflow: auto; } .awesome-box:nth-child(2) { aspect-ratio: 1 / 2; }

Второй способ вернуть указанное соотношение сторон — установить значение 0 для свойства min-height. Заменю им свойство overflow.
.awesome-box { width: 150px; aspect-ratio: 1; min-height: 0; }

То же самое сделаем в примере с флекс-элементами.
body { display: flex; gap: 1rem; } .awesome-box { width: 150px; aspect-ratio: 1; } .awesome-box:nth-child(2) { aspect-ratio: 1 / 2; }

Именование пользовательских CSS-свойств
В моей карьере был случай, когда я несколько часов тупил, не понимая, почему пользовательское CSS-свойство не работает. Для демонстрации я упростил код.
body { --color: green; background-color: var(--сolor); }
Вы, наверное, думаете, что цвет фона у элемента <body> должен быть зелёным? Я тоже так думал! Но нет, фон был белым.

Начав инспектировать код, я не обнаружил никаких ошибок.
Тем не менее, значение green почему-то не применялось к пользовательскому свойству --color. После долгих раздумий я плюнул и скопировал свойство --color и вставил его в функцию var(). И, о чудо, код заработал!
Оказывается, мы можем использовать в именовании пользовательских свойств как латинские, так и кириллические символы. Так вышло, что в строке --color: green я использовал букву «c» на английском, а в строке background-color: var(--сolor) — уже кириллическую «с». Для браузера это два совершенно разных пользовательских свойства, поэтому значение не применялось.
Признаться, я до сих пор не понимаю, почему кириллические символы в именах пользовательских свойств работают. Я пытался найти объяснение, но так и не нашёл ответа. Если вы знаете, пожалуйста, поделитесь информацией в комментариях. Мне было бы очень интересно узнать причину такого поведения.
Псевдо-класс :nth-child() с синтаксисом of S
На мой взгляд, синтаксис of S для псевдо-классов :nth-child() — одно из самых приятных нововведений в CSS за последние годы. Мне кажется, что каждому фронтенд-разработчику не хватало возможности более точного выбора элемента с определённым классом.
Однако, за моим первоначальным восторгом меня ждало разочарование. Перейду сразу к примеру.
<body> <div class="awesome-box">1</div> <div>2</div> <div class="awesome-block">3</div> <div class="awesome-box">4</div> <div class="awesome-block">5</div> </body>
:nth-child(2 of .awesome-box, .awesome-block) { outline: 0.3rem dashed lightblue; }

Я ожидал, что стили применятся ко второму элементу с классом .awesome-box, а затем ко второму элементу с классом .awesome-block, т.е. к двум последним элементам в примере. Но, как оказалось, это совсем не так.
Вся проблема заключается в понимании того, что именно представляет собой этот самый S. Согласно стандарту, это всего лишь список селекторов («). Как я понял, браузеры используют его в качестве перечня разрешённых селекторов. Сначала они находят все элементы, соответствующие этим селекторам, а затем уже среди них отбирают нужные.
Именно поэтому в моём случае браузеры сначала нашли все элементы с классами .awesome-box и .awesome-block, а затем применили стили к элементу, который оказался вторым в общем списке отобранных. Это был первый элемент с классом .awesome-block.
Значение absolute внутри грид-контейнера
Когда я смотрю обучающий материал по вёрстке, то часто слышу фразу: «Координаты для свойств top, right, bottom и left у элемента с position: absolute рассчитываются от элемента с нестатическим типом позиционирования».
Тут стоит пояснить, что такое нестатический тип позиционирования. Это любое значение для свойства position, кроме static. И да, это утверждение работает не всегда.
Я сразу перейду к примеру. Мы создадим два элемента. Родительский будет грид-контейнером с position: relative и сеткой из пяти колонок и четырёх строк. У дочернего будет установлено свойство position со значением absolute и координатами, заданными свойствами top и left.
.awesome-block { box-sizing: border-box; min-height: 100dvh; border: 3px solid currentColor; display: grid; grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(4, 1fr); position: relative; } .awesome-block::before { content: ""; width: 5rem; height: 5rem; background-color: darkolivegreen; position: absolute; top: 0; left: 0; }

Квадрат отображён в левом верхнем углу родительского элемента. Всё так, как нам говорили. А теперь смотрите магию.
Добавим свойства grid-column и grid-row для псевдо-элемента .awesome-block::before.
.awesome-block { box-sizing: border-box; min-height: 100dvh; border: 3px solid currentColor; display: grid; grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(4, 1fr); position: relative; } .awesome-block::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; }

Квадрат поменял свою позицию.
Так получилось, потому что свойствами grid-column и grid-row мы задали координаты прямоугольника, в который помещается элемент. Его же используют браузеры для отсчёта позиции элементов со свойством position и значением absolute.
Поэтому квадрат отобразился в левом верхнем углу прямоугольника, созданного строками grid-column: 2 / span 2 и grid-row: 2 / span 2, а не родительского элемента.
Мы можем даже сделать отступ от границ этой области. Например, добавим значение 1rem для свойств top и left.
.awesome-block { box-sizing: border-box; min-height: 100dvh; border: 3px solid currentColor; display: grid; grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(4, 1fr); position: relative; } .awesome-block::before { content: ""; width: 5rem; height: 5rem; background-color: darkolivegreen; grid-column: 2 / span 2; grid-row: 2 / span 2; position: absolute; top: 1rem; left: 1rem; }

Заключение
Подведём итог. В этой статье мы рассмотрели:
-
правила работы математической функции
calc; -
в каких ситуациях заданное соотношение сторон будет сломано;
-
нюансы работы синтаксиса
of Sдля псевдо-классовnth-childиnth-of-type; -
возможность задавать имена пользовательским CSS-свойствам на кириллице;
-
работу значения
absoluteвнутри грид-контейнера.
Также мне интересно узнать, какие у вас были случаи, когда CSS запутывал. Расскажите, пожалуйста, о них в комментариях.
На этом всё. Спасибо за чтение!
P.S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn’t magic. Присоединяйтесь. Ссылка в профиле.
© 2025 ООО «МТ ФИНАНС»
ссылка на оригинал статьи https://habr.com/ru/articles/937094/
Добавить комментарий