Привет, Хабр. Я продолжаю рассказывать про неизвестные широкому кругу разработчиков 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;
}

Отображается фрагмент статьи. Есть Заголовок первого уровня, за ним следует параграф с текстом. Далее идет заголовок второго уровня. За ними следует 2 параграфа с текстом, выделенные светло-голубым фоном

Честно говоря, этот код является давней моей профессиональной мечтой. Написать стили, указав только для части документа, это же круто. Считайте у нас появился прокаченный на стероидах селектор :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)


  1. fedechka
    21.01.2025 12:55

    Дочитал до слова "стеройд" и мысленно послал автора обратно в школу...


  1. Kuch
    21.01.2025 12:55

    Пожалуйста, читатель, пропусти пункт 3. А то кто знает, что в голову придет, а кому-то этот код потом дебадить. Надеюсь не мне