Привет, Хабр. Я продолжаю рассказывать про неизвестные широкому кругу разработчиков CSS фишки. Я отбираю их так, чтобы они были полезны в разного рода проектах. Неважно, верстаете ли вы сайт для малого бизнеса или создаёте супермодное React приложение. Они поддерживаются большинством браузеров. Отдельно отмечу, что я не считаю IE11 современным браузером. По этой причине я не учитывал его.
Сегодня мы рассмотрим:
- что вы можете не знать про псевдо-класс
:not()
; - примеры работы псевдо-класса
:has()
, работающие по логике операторов ИЛИ и И; - как неожиданно может повыситься специфичность правила при использовании псевдо-класса
:has()
; - чем полезно свойство
user-select
, кроме отмены выделения текста.
Больше не буду затягивать. Давайте посмотрим, что я вам подготовил.
▍ Что интересного скрывает псевдо-класс :not()
Общаясь с разработчиками, я понял, что псевдо-класc :not()
является одной из любимейших фишек CSS. Только не все знают одну особенность. Сейчас я вам её покажу.
Скорее всего, вы привыкли использовать этот псевдо-класс примерно следующим образом:
.link:not(:last-child) {
margin-left: 1rem;
}
Правило из этого кода применится ко всем элементам с классом .link
, кроме тех, которые являются последними элементами. Всё просто. Но давайте посмотрим на код с другой стороны.
По сути мы задали одно условие отбора, а именно «все, кроме последнего». Но псевдо-класс :not()
позволяется использовать множество условий. Для примера я покажу фрагмент кода из моего проекта.
img:not([class], :first-child) {
margin-block-start: var(--ds-typography-img-margin-block-start, var(--_ds-typography-main-margin))
}
Браузеры применят стили ко всем элементам img
кроме тех, у которых установлен атрибут class
, или они являются первым элементом. Другими словами, у меня существует два условия. Первое — «все, кроме тех, у которых есть атрибут class
». Второе — «все, кроме первого».
Такое поведение возможно, благодаря тому, что псевдо-класс :not()
принимает список селекторов. Как сказано в стандарте Selectors Level 4, это список из простых и сложных селекторов, разделённых запятыми. Вы также могли с ним столкнуться в псевдо-классах :is()
, :has()
и других.
Как я говорил, мы не ограничены количеством селекторов. Даже большинство селекторов отлично сработают. В качестве последнего «извращения» покажу пример с использованием псевдо-классов :not()
и :has()
. Я добавлю значение lightblue
для свойства background-color
только для тех элементов <p>
, у которых следующий соседний элемент не <h2>
.
<body>
<h1>Париж</h1>
<p>Столица и крупнейший город Франции. Находится на севере государства, в центральной части Парижского бассейна, на реке Сена</p>
<h2>История</h2>
<p>Париж вырос на месте поселения Лютеция, основанного кельтским племенем паризиев в III веке до н. э.</p>
<p>Поселение располагалось на безопасном острове Сите, окружённом водами реки Сены.</p>
</body>
p:not(:has(+ h2)) {
background-color: lightblue;
}
Честно говоря, этот код является давней моей профессиональной мечтой. Написать стили, указав только для части документа, это же круто. Считайте у нас появился прокаченный на стероидах селектор :not(:first-child)
или :not(:last-child)
.
▍ У псевдо-класса :has()
есть встроенная логика операторов ИЛИ и И
Псевдо-класс :has()
самое долгожданное новшество в CSS последних лет. Как я пониманию, не для меня одного. Много статей об этом псевдо-классе создаются чуть ли не каждую неделю. Конечно, большинство из них похожи друг на друга. И в них опускается несколько интересных нюансов. Что ж, надо вам про них рассказать.
Сразу начнём с кода, который я вам подготовил.
.awesome-block:has(h3) {
background-color: lightblue;
}
Я уверен, что видели что-то похожее у многих авторов. Здесь нет супер сложной магии. Браузеры применят стили, если в элементе с классом .awesome-block
, находится элемент <h3>
.
Только такой код является одним частным случаем из всех возможных. С псевдо-классом has()
можно сделать больше! Чтобы это показать, давайте углублённо рассмотрим принцип работы псевдо-класса.
В своём примере я использую селектор h3
в качестве аргумента. Но сам псевдо-класс принимает список селекторов, разделённых запятыми. По этой причине мы спокойно можем указывать множество селекторов. Например, я изменю код так, чтобы правило ещё срабатывало, если в элементе с классом .awesome-block
есть заголовок четвёртого уровня.
.awesome-block:has(h3, h4) {
background-color: lightblue;
}
Сейчас наше правило применится, когда есть хотя бы один из двух элементов. Другими словами, в этом случае браузеры работают по алгоритму логического оператора ИЛИ. Если есть элемент <h3>
ИЛИ <h4>
, то они применяют правило.
А это не вся логика, которая скрывается под псевдо-классом. Ещё есть возможность использовать логический оператор И. Для этого нужно использовать несколько псевдо-классов :has()
, указав их друг за другом.
.awesome-block:has(h3):has(h4) {
background-color: lightblue;
}
В этом примере браузеры применят правило только в том случае, если внутри элемента с классом .awesome-block
одновременно есть заголовки третьего и четвёртого уровня.
▍ Псевдо-класс :has()
неожиданно повышает специфичность всего правила
Скрытая логика является не единственным малоизвестным нюансом псевдо-класса :has()
. Ещё мы можем использовать его, как хак для повышения специфичности всего правила. Давайте сразу перейдём к примеру, чтобы мне было проще всё объяснить.
Сначала напишем разметку, в которой будет элемент <div>
с классом .awesome-block
, содержащий элемент <h3>
.
<body>
<div class="awesome-block">
<h3>Заголовок h3</h3>
</div>
</body>
Далее мы создадим одно правило, в котором будем использовать псевдо-класс :has()
.
.awesome-block:has(h3) {
background-color: lightblue;
}
Как браузеры рассчитают специфичность этого правила? Сначала рассчитывается специфичность псевдо-класса :has()
. Как написано в стандарте, она рассчитывается по самому приоритетному селектору среди всех переданных. В нашем примере передаётся только один селектор. Это селектор по типу h3
. Его специфичность — 0001
.
Далее это значение прибавляется к специфичности оставшийся части селектора. Мы используем селектор по классу. Его специфичность равняется 0010
. В итоге специфичность всего правила будет 0011
.
Теперь давайте попробуем переопределить это значение. Например, у нас появится ещё одно правило с такой же специфичностью.
.awesome-block:has(h3) {
background-color: lightblue;
}
div.awesome-block {
background-color: tomato; /* это правило выиграло! */
}
Поскольку специфичность правил одинаковая, применится то, которое является последним по порядку. В итоге в нашем примере браузеры применят значение tomato
для свойства background-color
.
К чему я всё это время вёл? Мы можем снова вернуть значение lightblue
, используя псевдо-класс :has()
. Достаточно повысить его специфичность, указав более «тяжёлый» селектор. Например, можно задать селектор по идентификатору.
.awesome-block:has(#unknown-selector, h3) {
background-color: lightblue; /* это правило выиграло! */
}
div.awesome-block {
background-color: tomato;
}
Стоп. У нас же нет в разметке элемента с атрибутом id
и значением #unknown-selector
. Да, да. В этом вся фишка.
Обрабатывая этот псевдо-класс, браузерам неважно, могут ли они найти с помощью него элементы. Они просто анализируют тип и выставляют значение специфичности. И больше ничего.
Конкретно в нашем примере браузеры сначала выберут из селекторов #unknown-selector
и h3
наиболее приоритетный. Это селектор #unknown-selector
. По этой причине «вес» всего псевдо-класса :has()
будет 0100
. К этому значению прибавится специфичность оставшийся части, а именно селектора по классу 0010
. В итоге будет рассчитана специфичность всего правила 0110
.
Конечно, я настоятельно не рекомендую использовать осознанно эту технику. Это хак. Но всегда есть вероятность того, что вы встретите такое решение в своей практике. А теперь вы осведомлены, и легко поймёте, почему у такого правила наивысший приоритет.
▍ Свойство user-select
делает мою работу проще
Свойство user-select
стало известно многим разработчикам после того, как они начали использовать его для отмены выделения текста у кнопок с помощью значения none
. Только эта ситуация не единственная, когда свойство полезно.
В моём примере будем использовать Хабр. Попробуем выделить элемент <input>
в тексте.
Для этого дважды кликнем по нему мышкой или нажмём и удержим палец на нём, если у вас сенсорный экран.
Мы видим, что выделилось только название. Но мне хочется, чтобы выделился весь элемент вместе со скобками. Хорошо, что такая задача решается одной строкой в CSS. Просто надо использовать свойство user-select
со значением all
.
В нашем примере я сделаю это прямо в инструментах разработчика. Для этого нужно найти элемент, который содержит название и скобки, и к нему добавить свойство. У нас это элемент <code>
.
Снова попробуем выделить элемент <input>
.
Великолепно! Текст выделился так, как я хотел изначально. И не надо дополнительно мучиться, выделяя скобки. Супер удобно!
▍ Заключение
Давайте подведём итог. В этой статье мы рассмотрели:
- возможность использовать несколько селекторов для псевдо-класса
:not()
; - как использовать псевдо-класс
:has()
, чтобы он работал по принципу логического оператора ИЛИ или И; - хак для повышения специфичности псевдо-класса
:has()
с помощью селектора по несуществующему элементу; - какую пользу несёт значение
all
для свойстваuser-select
.
Также, пожалуйста, напишите в комментариях, какие CSS фишки вы используете, о которых другие могут не знать. Буду ждать их. Спасибо за чтение!
P.S. Помогаю больше узнать про CSS в своём ТГ-канале CSS isn't magic. Присоединяйтесь. Ссылка в профиле.
P.S.S. Другие статьи из серии можно найти по тегу «sm909_unknown_css».
© 2025 ООО «МТ ФИНАНС»
Telegram-канал со скидками, розыгрышами призов и новостями IT ?
Комментарии (2)
Kuch
21.01.2025 12:55Пожалуйста, читатель, пропусти пункт 3. А то кто знает, что в голову придет, а кому-то этот код потом дебадить. Надеюсь не мне
fedechka
Дочитал до слова "стеройд" и мысленно послал автора обратно в школу...