Руководство по продвинутым CSS-селекторам. Часть 2

Перевод статьи «Guide to Advanced CSS Selectors — Part Two».

В первой части статьи мы рассмотрели тему специфичности и каскада, универсальный селектор, селекторы атрибутов, дочерний комбинатор, общий и соседний родственный комбинатор.

Во второй части статьи мы разберем продвинутые CSS-селекторы из категории псевдоклассов и псевдоэлементов, а также их применение на практике. Особенное внимание мы уделим синтаксису nth-child.

Мы рассмотрим:

Псевдоклассы

Это самая большая категория. Кроме того, она больше всего зависит от контекста.

Псевдокласс в CSS — это ключевое слово, добавленное к селектору, которое определяет его особое состояние или контекст.

Псевдоклассы очень сильно расширяют возможности CSS, добавляя функционал, который раньше ошибочно относили к одному лишь JavaScript.

Некоторые селекторы ориентируются на состояние элемента:

  • :focus
  • :hover
  • :visited
  • :target
  • :checked

Другие — на порядок расположения элементов:

  • :nth-child() / :nth-of-type()
  • :first-child / :first-of-type
  • :last-child / :last-of-type
  • :only-child / :only-of-type

Еще есть очень полезный псевдокласс :not(), недавно получивший поддержку :is(), а также псевдокласс :root, который постепенно выходит на передний план — по мере роста поддержки CSS-переменных.

Все доступные псевдоклассы, а также возможные опции можно посмотреть в документации MDN.

Применение псевдоклассов на практике

Таблица-зебра

Серия селекторов nth имеет бесчисленные варианты применения. Вы можете применять их везде, где вам нужно создать что-то вроде повторяющегося паттерна. Прекрасный пример — «полосатая» таблица.

Псевдокласс nth-child указывается с единственным аргументом, описывающим паттерн для выборки элементов. В качестве аргумента можно использовать число, функциональную запись или ключевые слова even и odd. Для наиболее эффективного создания полосок зебры мы используем именно ключевые слова:

tbody tr:nth-child(odd) {
  background-color: #ddd;
}

В результате мы получим такую таблицу:

Скриншот полосатой таблицы: чередуются белые и серые строки
Чередование цветов фона

При помощи функциональной записи для nth-child мы можем чередовать серию фоновых цветов и гарантировать, что узор будет повторяться в определенном порядке независимо от количества элементов. То есть паттерн из rebeccapurple, darkcyan и lightskyblue будет повторяться именно в таком порядке.

Работает это так. Вы определяете обще количество цветов (3), рядом с n, которая представляет все положительные числа, начиная от 0, и выступает в качестве множителя для указанного количества цветов. Таким образом, само по себе 3n выбирает 3-й (3 х 1), 6-й (3 х 2), 9-й (3 х 3) элемент и так далее. Первый в списке не выбирается, потому что 3 x 0 = 0.

Но в нашем паттерне первый выбранный элемент должен иметь первый цвет в палитре.

Поэтому мы расширяем запись до 3n + (число, соответствующее порядковому номеру цвета), и наше первое цветовое правило становится таким:

li:nth-child(3n + 1) {
  background-color: rebeccapurple;
}

Правило применяется к каждому третьему элементу, начиная с первого:

По существу, часть записи + [число] сдвигает начальный индекс.

Чтобы дополнить нашу палитру, мы определим следующие правила, увеличивая добавочное число. Это число соответствует порядковому номеру цвета в нашем повторяющемся паттерне:

li:nth-child(3n + 2) {
  background-color: darkcyan;
}

li:nth-child(3n + 3) {
  background-color: lightskyblue;
}

Итоговый результат выглядит так:

See the Pen Repeating Background Color Patterns With CSS nth-child by Stephanie Eckles (@5t3ph) on CodePen.

Расширенное руководство по nth-child можно почитать в справочнике рецептов от CSS-Tricks. Исследовать конструирование этих селекторов можно при помощи nth-child tester.

Удаление лишних отступов из дочерних элементов

Если вы не используете сброс, чтобы установить для всех элементов нулевой margin, вы можете столкнуться с тем, что типографические элементы создают дополнительные и нежелательные отступы, нарушающие баланс пространства в визуальном контейнере.

Псевдоклассы не всегда должны прилагаться к элементу напрямую. Это означает, что мы можем написать правило для любого элемента, который оказался последним потомком любого родителя, и убрать у этого элемента margin-bottom:

:last-child {
  margin-bottom: 0;
}
Исключение элементов из выборки

Осторожно применяя :not(), можно исключить элементы из выборки.

Мы уже рассматривали несколько вариантов использования :not() в первой части статьи (когда говорили о селекторах атрибутов). В частности, мы применяли a:not([class]) для захвата ссылок, к которым не применялись никакие классы.

:not() прекрасно подходит для использования во фреймворках или системах проектирования для повышения специфичности классов. Речь идет о классах, которые потенциально могут примениться к чему угодно и относительно которых известно, что в определенных комбинациях они могут быть источниками проблем.

Рассмотрим расширенный пример исключения ссылок с классами. Допустим, вы регулируете контраст для текста (возможно, в контексте темного режима) и хотите применить настройку контрастности также к текстовым ссылкам:

/* Non dark mode application */
a:not([class]) {
  color: blue;
}

/* Update text color for dark mode */
.dark-mode {
  color: #fff;
}

/* Extend the color update to links via `inherit` */
.dark-mode a:not([class]) {
  color: inherit;
}

Из селекторов :not() можно даже составить цепочку. Скажем, вы хотите создать правило для полей ввода в форме, но оно не должно касаться определенных типов:

input:not([type="hidden"]):not([type="radio"]):not([type="checkbox"])

Внутрь :not() можно поместить другой псевдоселектор. Например, чтобы исключить состояние :disabled для кнопок.

button:not(:disabled)

Таким образом вы сначала сбросите стили кнопки, и только затем примените цветовые стили, границы и пр. к неотключенным кнопкам — вместо последующего удаления этих стилей для button:disabled. Это позволит вам написать более аккуратные правила.

Эффективная выборка групп элементов

Псевдокласс :is(), недавно получивший поддержку, «принимает список селекторов как аргумент, и выбирает любой элемент, который может быть выбран одним из селекторов в этом списке» (документация MDN).

Этот псевдокласс позволяет более компактно выбирать элементы типографии:

:is(h1, h2, h3, h4)

Или более лаконично охватить стили макета:

:is(header, main, footer)

Мы даже можем скомбинировать :is() с :not() и выбрать, например, элементы, не являющиеся заголовками:

:not(:is(h1,h2,h3,h4))

Если вы хотите начать пользоваться этим селектором, пока нужно будет включать по крайней мере webkit-префикс версию. Из-за того, что браузеры порой весьма странно используют селекторы, вам нужно будет делать :is() уникальным правилом, отделенным от is(), чтобы браузер не выбросил оба правила.

:-webkit-any(header, main, footer)
Стиль элемента с якорной ссылкой

Когда для элемента создан якорь, позволяющий переходить прямо к этому элементу по якорной ссылке (например https://url.com/#якорь), вы можете стилизовать его при помощи :target.

Псевдокласс :target нужно добавлять к элементу, имеющему атрибут id. Но вы можете использовать его и в цепочке с селекторами потомков, чтобы охватить вложенные элементы. Например, можно задать больший размер для article:target h2.

Используя :target, я комбинирую его с псевдоэлементом ::before и таким образом добавляю маленькое дополнительное сообщение, которое помогает посетителю понять, что он видит элемент, на который вела ссылка. Выглядит это так (фраза «It’s me you’re looking for…»):

Бонусный совет. Обязательно добавьте небольшой отступ сверху элемента с прокруткой при помощи scroll-margin-top: 2em; (значение может быть любым). Это прогрессивное улучшение, так что непременно почитайте обзор поддержки браузеров для scroll-margin-top.

Визуальное обозначение посещенных архивных ссылок

Псевдокласс :visited уникален тем, что его могут использовать для нарушения приватности пользователя. Для решения этой проблемы производители браузеров ограничили стили, которые разрешено применять при помощи :visited.

На эту тему есть лонгрид от Юны Кравец «Hacking :visited».

Главное, что нужно знать, это что стили, примененные при помощи :visited, всегда будут использовать alpha-канал родительского элемента. Это значит, что вы не можете использовать rgba, чтобы перейти от невидимости к видимости: нужно менять все значение цвета.

Поэтому, чтобы скрыть начальное состояние, вам нужно иметь возможность использовать цельный цвет, например цвет фона страницы.

Кроме того, с точки зрения доступности может быть нежелательным, чтобы считывался контент псевдоэлемента, представляющего из себя значок или эмодзи, поскольку мы не можем добавить доступное имя для значения такого контента.

Вспомогательные технологии не достигли единообразия в отношении чтения содержимого псевдоэлементов. Мы можем попытаться заставить их игнорировать это содержимое при помощи aria-hidden="true".

В этом случае наш первый шаг — добавление span внутри ссылок. К нему мы и будем в конечном итоге применять стили при помощи :visited:

<a href="#">Article Title <span aria-hidden="true"></span></a>

Мы добавляем псевдоэлемент к стилю по умолчанию (non-visited) и делаем цвет таким же, как фон страницы, чтобы скрыть его визуально:

a span[aria-hidden]::after {
  content: "✔";
  color: var(--color-background); 
}

Затем, после посещения ссылки, мы обновляем цвет, чтобы сделать его видимым:

a:visited span[aria-hidden]::after {
  color: var(--color-primary); 
}
Продвинутая интерактивность при помощи :focus-within

Псевдокласс :focus-within — довольно многообещающий. Для него доступен полифил, но вообще пока что его нужно использовать с осторожностью, поскольку это прогрессивное улучшение.

Описание псевдокласса :focus-within в документации MDN:

«Псевдокласс :focus-within соответствует элементу, который либо сам находится в фокусе, либо содержит элемент, который находится в фокусе. Другими словами, он представляет элемент, который сам соответствует псевдоклассу :focus либо имеет потомка, который соответствует :focus».

Чтобы познакомиться с применением этого псевдокласса на практике, почитайте руководство по созданию доступного выпадающего меню на чистом CSS.

Псевдоэлементы

Photo by Max Bender on Unsplash

Псевдоэлементы позволяют стилизовать отдельные части выбранного элемента. Вообще псевдоэлементов довольно много, но в настоящее время лучше всего поддерживаются следующие:

  • ::after
  • ::before
  • ::first-letter
  • ::first-line
  • ::selection

Применение псевдоэлементов на практике

Дополнительные визуальные элементы для улучшения стиля

Псевдоэлементы ::before и ::after создают дополнительный элемент, который визуально кажется частью DOM, но на самом деле не является частью настоящего HTML DOM. Между тем, такие элементы стилизуются ничуть не хуже настоящих.

Я использовала эти псевдоэлементы для самых разных «украшательств». Поскольку они ведут себя подобно настоящим элементам, при использовании flexbox или CSS grid они вычисляются, как дочерние элементы, что сильно повышает их функциональность.

Несколько ключевых концепций, касающихся использования ::before и ::after:

  • Чтобы сделать их видимыми, требуется свойство content, но в качестве значения ему можно назначить пустую строку: content: "";
  • Не следует включать в значение content важное текстовое содержимое, поскольку вспомогательные технологии воспринимают это свойство по-разному.
  • Если не задана позиция, ::before будет отображаться до содержимого основного элемента, а ::after — после.

Вот иллюстрация поведения этих псевдоэлементов по умолчанию (с небольшой стилизацией):

Обратите внимание, что по умолчанию они ведут себя, как встроенные элементы, а для более длинного содержимого имеют поведение враппера:

А вот то же самое, но с добавлением display: flex к свойствам абзаца:

Меняем display: flex на display: grid:

Псевдоэлементы ::before и ::after — отличный способ быстрого добавления простых и единообразных типографских финтифлюшек. Примеры вы можете видеть в следующем демо:

See the Pen Headline Flourishes With CSS Pseudo Elements by Stephanie Eckles (@5t3ph) on CodePen.

Вы обратили внимание на прием с эмодзи?

С помощью функции attr() мы можем получить значение любого атрибута элемента для использования в свойстве content.

/*
<h2 class="emoji" data-emoji="😍">
*/
.emoji::before {
  content: attr(data-emoji);
}
Выделение лида статьи

«Лид (от англ. Lead paragraph — ведущий или главный абзац) — аннотация, «шапка» статьи, новости или пресс-релиза. Состоит из одного первого выделенного абзаца, в котором коротко формулируется проблема, раскрывается суть заголовка. Размер лида обычно не превышает 3-5 строк», — Википедия.

Для выделения первого абзаца текста мы можем применить комбинацию псевдокласса :first-of-type и псевдоэлемента :first-line. Любопытно, что получится динамическое решение, меняющее размер в привязке к области просмотра.

article p:first-of-type:first-line {
  font-weight: bold;
  font-size: 1.1em;
  color: darkcyan;
}

Адаптивный результат:

Контрастность для выделения текста (аспект доступности)

Стили для выделения текста часто упускают из виду, а между тем многие из нас по нескольку раз в день что-нибудь выделяют.

Хотя браузеры и пытаются управлять стилями этого события, контраст может потеряться. Я столкнулась с этим при использовании темной темы.

Чтобы решить эту проблему, мы можем задать собственные цвета текста выделения и фона при помощи псевдоэлемента ::selection:

::selection {
  background: yellow;
  color: black;
}
Собственные стили маркеров списка

::marker — перспективный псевдоэлемент, созданный специально для стилизации маркеров списка.

Я рассказывала о поддержке этого псевдоэлемента в браузерах и приводила пример использования в моем руководстве «Totally Custom List Styles».

Дополнительные ресурсы

  • Документация MDN по СSS-селекторам.
  • Selectors Explained — введите любой селектор, чтобы узнать, чего он касается.
  • Калькулятор CSS-специфичности от Polypane — здесь можно определить уровень специфичности селектора.
  • CSS Diner — игра для проверки навыков применения CSS-селекторов.
  • Style Stage — мой собственный проект. Здесь можно попрактиковаться в применении селекторов.

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Прокрутить вверх