Привет, Хабр. Я продолжаю делиться «косяками» CSS, которые могут сбить нас с толку. Мне не нравится, что многие плюются от него. Конечно, не без оснований. Я их понимаю, потому что тоже плевался.
Только мне нравится CSS. Хоть и потратил множество часов на изучение неочевидных моментов, я не хочу, чтобы у языка была слава «костыля». Подумав, как помочь другим меньше мучиться, я собираю и объясняю неочевидности CSS.
Сегодня мы рассмотрим:
- Нюансы работы синтаксиса
of S
для псевдо-класса:nth-child()
; - Случаи использования свойства
aspect-ratio
неожиданно ломающие вёрстку; - Различия между псевдо-классом
lang()
и селектором по атрибуту[lang=""]
.
Давайте посмотрим, что я вам подготовил.
▍ Подвох в синтаксисе of S
для псевдо-класса :nth-child()
Синтаксис of S для псевдо-классов :nth-child()
одна из моих любимейших нововведений в CSS за последние года. Мне кажется, что каждому фронтендеру не хватало возможности отобрать конкретный элемент определённого класса.
За моим восторгом меня ждало разочарование. Давайте на примере покажу вам.
<body>
<div class="primary">1</div>
<div>2</div>
<div class="secondary">3</div>
<div class="primary">4</div>
<div class="secondary">5</div>
</body>
:nth-child(2 of .primary, .secondary) {
outline: 0.3rem dashed coral;
}
У меня к вам вопрос: «К каким элементам применятся стили?». Я думал, что сначала ко второму элементу с классом .primary
, а потом ещё ко второму элементу с классом .secondary
. Оказывается, это не так.
Нюанс, сбивший меня с толку, заключается в том, что такое S. В стандарте сказано, что этот символ является списком селекторов (<complex-selector-list>
). Как я понимаю, браузеры используют его, как список «разрешённых» селекторов. С помощью него они находят все подходящие элементы, а потом среди них отбирают нужные.
По этой причине в моём случае браузеры сначала нашли все элементы с классами .primary
и .secondary
, а потом среди них применили стили к элементу, являющимся вторым отобранным, и первым с классом .secondary
.
Конечно, от этой особенности я не поменял отношение к синтаксису. Я часто его использую. Главное теперь помнить, как же он работает.
▍ В каких случаях свойство aspect-ratio
ломает вёрстку
Свойство aspect-ratio
было одним из неизвестных мне. Конечно, я знал, что оно делает. Но без деталей. Казалось, что сложностей при работе с ним нет. Давайте посмотрим, так ли это.
В стандарте CSS Box Sizing Module Level 4 сказано, что свойство устанавливает предпочтительное соотношение сторон (preferred aspect ratio
). Оно будет использоваться для вычисления автоматических значений размеров и в других задачах при компоновке страницы.
В качестве примера создам квадратный элемент .awesome-box
.
<body>
<div class="awesome-box"></div>
</body>
.awesome-box {
width: 150px;
aspect-ratio: 1;
}
Вроде всё действительно просто. На всякий случай добавлю текст для элемента.
<body>
<div class="awesome-box">
<p>
The aspect-ratio CSS property allows you to define the desired width-to-height ratio of an element's box. This means that even if the parent container or viewport size changes, the browser will adjust the element's dimensions to maintain the specified width-to-height ratio.
</p>
</div>
</body>
Тут уже наш квадрат перестал быть таковым. Может показаться, что это ошибка. Это не так. Все значения свойства учитывают разные условия, влияющие на размеры элемента. Контент —
одно из них. Если он должен занимать больше места, то это предпочтительное соотношение сторон сломается.
Другим примером являются элементы, находящиеся внутри флекс-контейнера.
<body>
<div class="container">
<div class="awesome-box">1/1</div>
<div class="awesome-box">1/2</div>
</div>
</body>
.container {
display: flex;
gap: 1rem;
}
.awesome-box {
width: 150px;
aspect-ratio: 1;
}
.awesome-box:nth-child(2) {
aspect-ratio: 1 / 2;
}
По умолчанию флекс-элементы растягиваются по дополнительной оси. Если к ним ещё применяется свойство aspect-ratio
, оно сработает по-особому. У всех элементов будет установлено то значение, в результате которого по дополнительной оси будет максимальное значение.
В нашем примере размер по дополнительной оси у первого элемента 150px
, поскольку у него установлено значение 1
для свойства aspect-ratio
. Для второго элемента я использую значение 1/2
, поэтому значение по дополнительной оси будет 300px
. Оно больше первого, поэтому браузеры применяют значение 1/2
для всех флекс-элементов.
Если мы добавим контент, то он также может нарушить предпочтительное соотношение сторон.
В стандарте описаны несколько случаев, заставляющих сохранить указанное значение. В первом используется свойство overflow
со значением auto
. Его нужно добавить к элементу, у которого задано свойство aspect-ratio
.
Сделаем это для элемента .awesome-box
из первого примера.
.awesome-box {
width: 150px;
aspect-ratio: 1;
overflow: auto;
}
Внутри флекс-контейнера мы тоже можем использовать этот способ. Только будет одна особенность. Браузеры вернут максимальное соотношение сторон для всех флекс-элементов.
.container {
display: flex;
gap: 1rem;
}
.awesome-box {
width: 150px;
aspect-ratio: 1;
overflow: auto;
}
.awesome-box:nth-child(2) {
aspect-ratio: 1 / 2;
}
Вторым случаем является свойство min-height
. Если мы объявим его со значением 0
, то свойство aspect-ratio
заработает. Давайте заменим свойство overflow
из первого примера.
.awesome-box {
width: 150px;
aspect-ratio: 1;
min-height: 0;
}
В примере с флекс-контейнером свойство также заставляет работать свойство aspect-ratio
.
.container {
display: flex;
gap: 1rem;
}
.awesome-box {
width: 150px;
aspect-ratio: 1;
min-height: 0;
}
.awesome-box:nth-child(2) {
aspect-ratio: 1 / 2;
}
Забавно ещё то, что если мы изменим направление осей, то результат будет тем же. Для примера я добавил свойство flex-direction
со значением column
.
.container {
display: flex;
flex-direction: column;
gap: 1rem;
}
.awesome-box {
width: 150px;
aspect-ratio: 1;
min-height: 0;
}
.awesome-box:nth-child(2) {
aspect-ratio: 1 / 2;
}
Все эти моменты сказались на моё отношение к свойству aspect-ratio
. Я чаще избегаю его, чем использую. Исключением является изображения и видео. Об этом я рассказал в другой статье.
▍ Разница между псевдо-классом :lang()
и селектором по атрибуту [lang=""]
В августе я опубликовал статью «Неизвестно полезный CSS. Часть 4». В ней поделился псевдо-классом :lang()
. В комментариях были люди, которые подумали, что его можно заменить селектором по атрибуту [lang=""]
. В общем, всё получилось, как у меня при первом знакомстве с псевдо-классом. Конечно, тогда я ошибался.
В стандарте «Selectors Level 4» дано определение. Псевдо-класс :lang
представляет элемент, язык контента которого является одним из языков, перечисленных в его аргументе. Другими словами, браузеры найдут абсолютно все элементы, у которого установлен язык, указанный в качестве аргумента. Начнут с элемента, у которого установлен атрибут lang
, а закончат последним дочерним.
Браузеры применят свойство background-color
абсолютно ко всем элементам в следующем примере.
<body>
<section lang="en">
<h2>The :lang() pseudo-class</h2>
<div>
<p>Псевдо-класс CSS :lang() выбирает элементы, основываясь на языке, на котором они определены</p>
</div>
</section>
</body>
Если мы установим атрибут lang
с другим значением для дочернего элемента, то с него браузеры перестанут применять стили. Для примера я установлю lang="ru"
для элемента <div>
.
<body>
<section lang="en">
<h2>The :lang() pseudo-class</h2>
<div lang="ru">
<p>Псевдо-класс CSS :lang() выбирает элементы, основываясь на языке, на котором они определены</p>
</div>
</section>
</body>
А вот с селектором по атрибуту такого поведения не будет. Стили применятся только к элементу, у которого установлен атрибут lang
.
Есть ещё одно отличие. Правда, маловероятно, что вы его встретите на практике. Но мне было любопытно узнать о нём.
В стандарте есть ссылка на документ RFC4647. Как я понял, он описывает правила, по которым браузеры понимают язык контента. Оказывается, объявленное значение для атрибута lang
является тегом языка.
Мы не будем подробно их рассматривать. Для статьи нам важно узнать, что теги состоят из подтегов. Они помогают определить различные диалекты и их правила.
Разница между псевдо-классом lang()
и селектором по атрибуту заключается в том, что первый найдёт элементы на указанном языке, включая все диалекты. По этой причине в следующем примере свойство background-color
применяется для двух элементов, а свойство font-weight
только для второго.
<body>
<p lang="de-CH">Dies ist ein Wert</p>
<p lang="de">Dies ist ein Wert</p>
</body>
:lang(de) {
background-color: pink;
}
[lang="de"] {
font-weight: bold;
}
Можно заморочиться и заставить селектор по атрибуту найти оба элемента, но если вы прочитаете правила формирования тегов языка из стандарта RFC4647, то поймёте, что это плохая идея.
Подводя итоги раздела, мне хочется сказать, что псевдо-класс :lang()
сильно недооценён. Мне он показался бесполезным, поэтому я не знал о нюансах. Так что этот случай стал для меня хорошим напоминанием, что нужно быть любознательным.
▍ Заключение
Давайте подведём итог. В этой статье мы рассмотрели следующее:
- Браузеры при помощи синтаксиса
of S
сначала находят все элементы, а только потом среди них находят нужный; - По умолчанию значения свойства
aspect-ratio
можно нарушить с помощью контента, но свойстваoverflow
иmin-height
отменяют это поведение; - Внутри флекс-контейнера заданное с помощью свойства
aspect-ratio
соотношение сторон рассчитывается по максмальному значению по дополнительной среди всех флекс-элементов; - Псевдо-класс
lang()
срабатывает для элемента, у которого задан атрибутlang
, а также для всех его дочерних элементов, пока среди них не встретится другой элемент с атрибутомlang
; - Также этот псевдо-класс срабатывает для всех диалектов.
Оставляю ссылки на все выпуски:
Также, пожалуйста, напишите в комментариях, какие у вас были случаи, когда CSS сбил вас с толку. Буду ждать их. Спасибо за чтение!
P.S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn't magic. Присоединяйтесь. Ссылка в профиле
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
Qoragar
Drop-shadow
+ анимированныйclip-path
. Внезапно столкнулся несколько лет назад.Казалось бы, всё норм, тенька отлично ложится на любой элемент с
clip-path
. Но как толькоclip-path
анимируешь... Упс,drop-shadow
говорит "я устала, я ухожу" — после начала анимации просто исчезает, и снова появляется на элементе только в финальном состоянии анимации. Наблюдалось во всех браузерах.Методом (не)научного тыка удалось выяснить, что тенька работает на всём протяжении анимации, только если обернуть элемент с
clip-path
в дополнительный внешний контейнер-пустышку, назначивdrop-shadow
уже на него. Зачем, почему — сие науке так до сих пор и неизвестно...Особую пикантность ситуации придаёт тот факт, что аккуратно и чётко работать по границам clip-path умеет только
drоp-shadow
— никакие другие виды теней не справляются.director-rentv
Подобный нюанс видал как минимум в Firefox с backdrop-filter: blur. Если установить его элементу, а затем анимировать его передвижение через Web Animation API, то на время анимации блюр исчезает (всё чёткое), а в моменты остановки - снова появляется.
Qoragar
А попробуйте на внешнем контейнере-"обёртке", как я выше описал. Интересно, это какой-то общий косяк фильтров, или только определённых?