Наверняка каждый, кто работал с CSS, не раз впадал в отчаяние. Например, когда пытался отцентрировать дочерний элемент по отношению к родительскому, что является весьма нетривиальной задачей. В этой статье мы рассмотрим ещё более сложную тему — псевдоклассы, один из видов селекторов.
Прим. перев. Псевдоклассы предназначены для изменения стиля существующих элементов страницы в зависимости от их динамического состояния, например, при работе со ссылками (:link
, :visited
, :hover
, :active
, :focus
).
В этой статье мы познакомимся с двумя типами псевдоклассов:
- селекторы
*-of-type
; - селекторы
*-child
.
Иногда сложно понять, что такое псевдоклассы и как они работают. Главная сложность состоит в том, что они представлены в абстрактном виде. Поэтому для упрощения восприятия использованы схемы с DOM-деревьями.
Разметка и DOM-дерево
Обратите внимание на HTML-код, представленный ниже. Он будет использоваться во всех примерах в этой статье.
<body>
<div class=”main”>
<a href=”#”>Внутренняя ссылка 1</a>
<a href=”#”>Внутренняя ссылка 2</a>
<ul>
<a href=”#”>Вложенная в список ссылка 1</a>
<li>
<a href=”#”>Элемент списка 1</a>
</li>
<li>
<a href=”#”>Элемент списка 2</a>
</li>
</ul>
<a href=”#”>Внутренняя 3</a>
</div>
<a href=”#”>Внешняя ссылка 1</a>
<a href=”#”>Внешняя ссылка 2</a>
</body>
А теперь давайте преобразуем этот код в нечто, понятное визуально и интуитивно — в DOM-дерево.
Давайте рассмотрим подробнее элемент body
. В нём расположено 3 дочерних элемента: main
и два элемента <a>
.
<body>
<div class=”main”>
...
</div>
<a href=”#”>Внешняя ссылка 1</a>
<a href=”#”>Внешняя ссылка 2</a>
</body>
На схеме представлено отношение между body
и дочерними элементами.
Важен порядок, в котором дочерние элементы размещаются в дереве. В коде дочерние элементы располагаются сверху вниз, а в дереве — слева направо.
Теперь давайте посмотрим на div-контейнер с классом main
:
<div class=”main”>
<a href=”#”>Внутренняя ссылка 1</a>
<a href=”#”>Внутренняя ссылка 2</a>
<ul>
...
</ul>
<a href=”#”>Внутренняя ссылка 3</a>
</div>
Контейнер с классом main
имеет 4 дочерних элемента: сначала два элемента <a>
, затем элемент ненумерованного списка ul
и снова <a>
-элемент.
Аналогичным образом спускаемся вниз по схеме согласно порядку вложенности элементов, отрисовывая полноценное дерево HTML-кода.
Внимательно изучите структуру DOM-дерева, чтобы легче воспринимать оставшуюся часть статьи.
Псевдокласс only-of-type (единственный из выбранного типа)
Для всех псевдоклассов действует один и тот же формат:
выбираемый-элемент:селектор { /* стиль */ }
Выбираемый элемент служит для того, чтобы выбрать любой элемент из DOM. Обратите внимание на пример:
a:only-of-type {
border: 2px solid black;
}
В приведённом выше фрагменте кода выбираемый элемент — тег <a>
, а его селектор — one-of-type
.
Данный пример можно изучить в работе на codepen.io. Уберите символы комментариев, чтобы посмотреть, как работает тот или иной селектор, а лучше продолжите чтение статьи, чтобы не запутаться.
Начнём с того, что выберем всё, что есть в DOM, а затем отфильтруем.
Обратите внимание, как был произведён выбор: в каждой секции (от 1 до 5) у элементов есть общий родительский элемент. Пример: родительский элемент для первой секции — body
, для второй секции — контейнер с классом main
, и т. д. Каждая секция соответствует уровню вложенности кода.
Так как в нашем случае выбираемым элементом являются элементы <a>
, то мы можем сделать следующее:
Мы выбрали все элементы <a>
в каждой секции и последовательно их пронумеровали слева направо. Не забывайте, что порядок очень важен.
Теперь мы завершаем часть работы, связанную с выбираемым элементом, и приступаем к фильтрации.
Only-of-type
проходит через все секции, выбирая только те элементы <a>
, которые являются единственными для своей секции.
Обратите внимание, что <a>
-элементы для первой и второй секций не были выбраны из-за того, что они не являются единственными для своих секций. Иначе говоря: одна секция — один элемент <a>
.
Псевдокласс first-of-type (первый из выбранного типа)
Давайте продолжим с того, на чём остановились — с выбираемого элемента (речь идёт об <a>
-теге).
Селектор first-of-type
выбирает каждый первый элемент <a>
в секции.
Код, который выполняет условия приведённой выше схемы:
a:first-of-type {
border: 2px solid black;
}
Вы можете посмотреть как работает этот код в браузе на codepen.io.
Псевдокласс last-of-type (последний из выбранного типа)
Last-of-type
— полная противоположность псевдокласса first-of-type
. Этот селектор выбирает последние элементы <a>
во всех секциях.
Элемент <a>
— первый и последний для секций, в которых он представлен в единственном числе.
Псевдокласс nth-of-type (n-й из выбранного типа)
Теперь переходим к наиболее интересной части статьи. Рассмотрим простой CSS с элементами математики из школьной программы.
Давайте определим следующий стиль, чтобы посмотреть на селектор в действии:
a:nth-of-type(1) {
border: 2px solid black;
}
Вместо nth
подставьте число из скобок в порядковой форме, чтобы прочитать правильно селектор.
Итак, давайте вернёмся к селектору. a:nth-of-type(1)
может читаться точно так же, как и a:first-of-type
. В данном случае эти селекторы работают одинаково: каждый из них выбирает только те элементы <a>
, которые являются первыми в своих секциях.
А теперь давайте попробуем кое-что другое:
a:nth-of-type(0) {
border: 2px solid black;
}
Надеюсь, вы догадались, что произошло. Если нет, то объясняю: в этом случае ни один элемент <a>
не будет выбран, так как счёт начинается с 1, а не с 0. То же самое произойдёт, если вы напишете a:nth-of-type(5)
или a:nth-of-type(6/7/8)
вместо a:nth-of-type(0)
. Это легко можно объяснить: в DOM нет 5-го, 6-го, 7-го или 8-го элемента <a>
, поэтому в данном случае селектор ничего не выберет.
А если мы пойдём немного глубже и напишем следующее условие:
a:nth-of-type(2) {
border: 2px solid black;
}
то селектор выберет каждый второй элемент <a>
в первой и во второй секциях.
Для полного понимания картины приведу ещё один пример:
a:nth-of-type(3) {
border: 2px solid black;
}
В этом случае будет выбран третий (не перепутайте с a:last-of-type
) элемент <a>
во второй секции, так как эта секция — единственная, в которой есть три элемента <a>
.
Достаточно просто, не так ли? Но вставлять в скобки вы можете не только числа, но и формулы. Например, (a*n)+b
(то же самое, что и an + b
), где a
и b
— константы, а n
—значение, которое больше или равно нулю. Не переживайте, если вам что-то непонятно, сейчас я всё объясню.
Для начала применим следующий стиль:
a:nth-of-type(n) {
border: 2px solid black;
}
Формула, переданная в селектор выглядит следующим образом: (1 * n) + 0 [=n]
, где а = 1
, b = 0
, n
— переменная. Теперь давайте разберёмся, что идёт дальше. Значение n
последовательно вставляется в формулу, начиная с 0, после этого селектор делает выбор. Поэтому a:nth-of-type
можно представить следующим образом:
a:nth-of-type(0) { border: 2px solid black; } // n = 0
a:nth-of-type(1) { border: 2px solid black; } // n = 1
a:nth-of-type(2) { border: 2px solid black; } // n = 2
a:nth-of-type(3) { border: 2px solid black; } // n = 3
a:nth-of-type(4) { border: 2px solid black; } // n = 4...
В соответствии с данными результатами и будет выбран элемент <a>
.
Давайте приведём ещё один пример:
a:nth-of-type(2n + 1) {
border: 2px solid black;
}
При постепенном заполнении значений n
с нуля будут генерироваться следующие селекторы:
// n = 0 подразумевает эту операцию: (2 * 0) + 1 = 1
a:nth-of-type(1) { border: 2px solid black; }// n = 1 подразумевает эту операцию: (2 * 1) + 1 = 3
a:nth-of-type(3) { border: 2px solid black; }// n = 2 подразумевает эту операцию: (2 * 2) + 1 = 5 – Но в данном случае ни один элемент выбран не будет, так как у нас нет 5-го элемента <a> ни в одной из секций.
a:nth-of-type(5) { border: 2px solid black; }...
Помимо чисел и формул, генерирующих числа, вы можете также выбирать чётные или нечётные номера элементов. Even
выбирает все чётные номера элемента в соответствующих секциях. Предположим, что у нас есть секция с 4-мя элементами <a>
. Из этой секции селектор выберет второй и четвёртый элементы <a>
. Аналогичным образом селектор работает с нечётными числами, только следует заменить even
на odd
— a:nth-of-type(odd) {style}
Псевдокласс nth-last-of-type (n-й с конца из выбранного типа)
Этот селектор работает точно так же, как и предыдущий, но с небольшим отличием:
a:nth-last-of-type(1) {
border: 2px solid black;
}
Обратите внимание, что в каждой секции нумерация элементов <a>
идёт справа налево. Это и есть единственное отличие от предыдущего селектора. В :last-of-type
также можно использовать числа, формулы и выбор по чётным и нечётным номерам элементов. Главное запомнить, что для этого селектора работает обратный отбор, его следует читать справа налево. Иначе говоря, последний элемент превращается в первый, предпоследний превращается во второй и т. д.
На этом часть статьи о селекторах *-of-type
подходит к концу. Надеюсь, что вам понравилось. Мы проделали большой путь от only-of-type
до nth-last-of-type
, углубившись в first-of-type
, last-of-type
и nth-of-type
. Если где-то в середине вы что-то не до конца поняли, то предлагаю вам поэкспериментировать в codepen и перечитать эту часть.
Настало время перейти к новой категории псевдоклассов — *-child
. В этой части статьи мы рассмотрим и попытаемся понять, как работает эта категория селекторов. После того, как вы изучили предыдущие селекторы, эта категория псевдоклассов не покажется вам трудной. Давайте начнём!
Псевдокласс only-child (единственный из выбранного типа дочернего элемента)
Примените следующий стиль к HTML-коду, который дан в самом начале .
a:only-child {
border: 2px solid black;
}
Как вы видите, селектор выбрал два <a>
-элемента, каждый из которых вложен в элемент «список» li
. Почему так и как это работает: only child дословно переводится как «единственный ребёнок / единственный дочерний элемент», поэтому селектор выбирает только те <a>
-элементы, у которых родительский элемент имеет только один дочерний элемент —<a>
. В нашем случае каждый li
имеет по одному родительскому и по одному дочернему элементу, поэтому они и были выбраны.
Почему не выбран <a>
-элемент, единственный дочерний элемент <a>
ненумерованного списка ul
? Ответ прост: несмотря на то, что он является единственным дочерним <a>
-элементом, у него есть ещё два соседа, не являющихся элементами <a>
, для которых ul
является родительским. Получается три дочерних элемента, что, разумеется, не попадает под наше правило: один родительский элемент — один дочерний.
Псевдокласс first-child (первый дочерний элемент)
Примените следующий стиль:
a:first-child {
border: 2px solid black;
}
Селектор first-child
выбирает все первые дочерние элементы <a>
своих родителей. Следует помнить, что он выбирает только первые дочерние <a>
-элементы. Это означает, что элемент <a>
должен занимать первую позицию в своей секции, а не быть первым после какого-либо другого дочернего элемента.
Если вы не поняли, почему первый дочерний <a>
-элемент в body
не был выбран, то сейчас объясню. Дело в том, что он является первым только среди дочерних <a>
-элементов в этой секции. Среди всех дочерних элементов он — второй, так как первый — это контейнер с классом main
.
Псевдокласс last-child (последний дочерний элемент)
Думаю, что после всего материала, который мы изучили, вы примерно понимаете, как работает этот селектор. Если нет — объясняю: last-child
выбирает последний элемент <a>
в каждой секции. Правило прежнее: <a>
-элемент должен быть именно первым в списке всех дочерних элементов, иначе он не будет выбран.
Обратите внимание на схему после кода.
a:last-child {
border: 2px solid black;
}
Псевдокласс nth-child (n-й дочерний элемент)
Надеюсь, вы усвоили небольшой урок математики в первой части статьи. Потому что сейчас это произойдёт снова, но с небольшими изменениями.
Обратите внимание на следующий пример:
a:nth-child(1) {
border: 2px solid black;
}
Всё работает так же, как и с селектором nth-of-type
.
Чтобы начать использовать nth-child
, нужно повторить все те действия, которые мы произвели с селектором nth-of-type
— поместить в скобки любое число. В примере выше селектор сработает точно так же, как и first-child
— выберет все первые элементы <a>
в секциях.
Итак, давайте перейдём к новому примеру:
a:nth-child(2n - 1) {
border: 2px solid black;
}
Постепенно добавляем значения n
в формулу начиная с 0, и это даёт нам понять, что селектор сверху во многом схож тем, что ниже.
// n = 0 Подразумевает эту операцию: (2 * 0) - 1 = 0 - 1 = -1
a:nth-child(-1) { border: 2px solid black; } | не выбирает ничего// n = 1 подразумевает эту операцию: (2 * 1) - 1 = 2 - 1 = 1
a:nth-child(1) { border: 2px solid black; }// n = 2 подразумевает эту операцию: (2 * 2) - 1 = 4 - 1 = 3
a:nth-child(3) { border: 2px solid black; }// n = 3 подразумевает эту операцию: (2 * 3) - 1 = 6 - 1 = 5
a:nth-child(5) { border: 2px solid black; } | не выбирает ничего
Если селектор получает числа, выходящие за пределы секторов (как -1, 5, 6), он просто их игнорирует.
На схеме ниже показано действие селектора a:nth-child(2n-1)
:
На сайте CSS Tricks вы сможете найти очень полезную и исчерпывающую статью о селекторе :nth-child
.
А пока что давайте перейдём к последнему селектору в нашем руководстве.
Псевдокласс nth-last-child (n-й дочерний элемент с конца)
Этот селектор работает точно так же, как и :nth-child
, за исключением одной тонкости: он видит и читает дерево DOM в обратном направлении — справа налево.
Дочерние элементы в каждой секции для этого селектора выстраиваются в ряд справа налево.
a:nth-last-child(1) {
border: 2px solid black;
}
Обратите внимание на то, как расположены и выбраны дочерние элементы на схеме:
Как видите, всё довольно просто — выбор элемента происходит с конца.
Заключение
На этом наше знакомство с псевдоклассами окончено. Но вы всегда можете найти полезный материал по CSS у нас на сайте. Желаем вам удачи в освоении CSS!
Перевод статьи «How CSS pseudo-classes work, explained with code and lots of diagrams»