July 3, 2019

Знаете ли вы селекторы?

CSS3-селекторы – фундаментально полезная вещь.

Даже если вы почему-то (старый IE?) не пользуетесь ими в CSS, есть много фреймворков для их кросс-браузерного использования CSS3 из JavaScript.

Поэтому эти селекторы необходимо знать.

Основные виды селекторов

Основных видов селекторов всего несколько:

  • * – любые элементы.
  • div – элементы с таким тегом.
  • #id – элемент с данным id.
  • .class – элементы с таким классом.
  • [name="value"] – селекторы на атрибут (см. далее).
  • :visited – «псевдоклассы», остальные разные условия на элемент (см. далее).

Селекторы можно комбинировать, записывая последовательно, без пробела:

  • .c1.c2 – элементы одновременно с двумя классами c1 и c2
  • a#id.c1.c2:visited – элемент a с данным id, классами c1 и c2, и псевдоклассом visited

Отношения

В CSS3 предусмотрено четыре вида отношений между элементами.

Самые известные вы наверняка знаете:

  • div p – элементы p, являющиеся потомками div.
  • div > p – только непосредственные потомки

Есть и два более редких:

  • div ~ p – правые соседи: все p на том же уровне вложенности, которые идут после div.
  • div + p – первый правый сосед: p на том же уровне вложенности, который идёт сразу после div (если есть).

Посмотрим их на примере HTML:

<h3>Балтославянские языки</h3>

<ol id="languages">
  ...Вложенный OL/LI список языков...
</ol>

CSS-селекторы:

/*+ no-beautify */
#languages li {
  color: brown;   /* потомки #languages, подходящие под селектор  LI */
}

#languages > li {
  color: black;   /* первый уровень детей #languages подходящих под LI */
}

#e-slavic { font-style: italic; }

#e-slavic ~ li {  /* правые соседи #e-slavic с селектором LI */
  color: red;
}

#latvian {
  font-style: italic;
}

#latvian * {      /* потомки #latvian, подходяще под * (т.е. любые) */
  font-style: normal;
}

#latvian + li {  /* первый правый сосед #latvian с селектором LI */
 color: green;
}

Результат:

Фильтр по месту среди соседей

При выборе элемента можно указать его место среди соседей.

Список псевдоклассов для этого:

  • :first-child – первый потомок своего родителя.

  • :last-child – последний потомок своего родителя.

  • :only-child – единственный потомок своего родителя, соседних элементов нет.

  • :nth-child(a) – потомок номер a своего родителя, например :nth-child(2) – второй потомок. Нумерация начинается с 1.

  • :nth-child(an+b) – расширение предыдущего селектора через указание номера потомка формулой, где a,b – константы, а под n подразумевается любое целое число.

    Этот псевдокласс будет фильтровать все элементы, которые попадают под формулу при каком-либо n. Например: -:nth-child(2n) даст элементы номер 2, 4, 6…, то есть чётные.

    • :nth-child(2n+1) даст элементы номер 1, 3…, то есть нечётные.
    • :nth-child(3n+2) даст элементы номер 2, 5, 8 и так далее.

Пример использования для выделения в списке:

/*+ hide="CSS к примеру выше"  no-beautify */
li:nth-child(2n) { /* чётные */
  background: #eee;
}

li:nth-child(3) {  /* 3-ий потомок */
  color: red;
}
  • :nth-last-child(a), :nth-last-child(an+b) – то же самое, но отсчёт начинается с конца, например :nth-last-child(2) – второй элемент с конца.

Фильтр по месту среди соседей с тем же тегом

Есть аналогичные псевдоклассы, которые учитывают не всех соседей, а только с тем же тегом:

  • :first-of-type
  • :last-of-type
  • :only-of-type
  • :nth-of-type
  • :nth-last-of-type

Они имеют в точности тот же смысл, что и обычные :first-child, :last-child и так далее, но во время подсчёта игнорируют элементы с другими тегами, чем тот, к которому применяется фильтр.

Пример использования для раскраски списка DT «через один» и предпоследнего DD:

/*+ hide="CSS к примеру выше"  no-beautify */
dt:nth-of-type(2n) {
  /* чётные dt (соседи с другими тегами игнорируются) */
  background: #eee;
}

dd:nth-last-of-type(2) {
  /* второй dd снизу */
  color: red;
}

Как видим, селектор dt:nth-of-type(2n) выбрал каждый второй элемент dt, причём другие элементы (dd) в подсчётах не участвовали.

Селекторы атрибутов

На атрибут целиком:

  • [attr] – атрибут установлен,
  • [attr="val"] – атрибут равен val.

На начало атрибута:

  • [attr^="val"] – атрибут начинается с val, например "value".
  • [attr|="val"] – атрибут равен val или начинается с val-, например равен "val-1".

На содержание:

  • [attr*="val"] – атрибут содержит подстроку val, например равен "myvalue".
  • [attr~="val"] – атрибут содержит val как одно из значений через пробел.

Например: [attr~="delete"] верно для "edit delete" и неверно для "undelete" или "no-delete".

На конец атрибута:

  • [attr$="val"] – атрибут заканчивается на val, например равен "myval".

Другие псевдоклассы

  • :not(селектор) – все, кроме подходящих под селектор.

  • :focus – в фокусе.

  • :hover – под мышью.

  • :empty – без детей (даже без текстовых).

  • :checked, :disabled, :enabled – состояния INPUT.

  • :target – этот фильтр сработает для элемента, ID которого совпадает с анкором #... текущего URL.

    Например, если на странице есть элемент с id="intro", то правило :target { color: red } подсветит его в том случае, если текущий URL имеет вид http://...#intro.

Псевдоэлементы ::before, ::after

«Псевдоэлементы» – различные вспомогательные элементы, которые браузер записывает или может записать в документ.

При помощи псевдоэлементов ::before и ::after можно добавлять содержимое в начало и конец элемента:

<style>
  li::before {
    content: " [[ ";
  }

  li::after {
    content: " ]] ";
  }
</style>

Обратите внимание: содержимое добавляется <b>внутрь</b> LI.

<ul>
  <li>Первый элемент</li>
  <li>Второй элемент</li>
</ul>

Псевдоэлементы ::before/::after добавили содержимое в начало и конец каждого LI.

:before или ::before?

Когда-то все браузеры реализовали эти псевдоэлементы с одним двоеточием: :after/:before.

Стандарт с тех пор изменился и сейчас все, кроме IE8, понимают также современную запись с двумя двоеточиями. А для IE8 нужно по-прежнему одно.

Поэтому если вам важна поддержка IE8, то имеет смысл использовать одно двоеточие.

Практика

Вы можете использовать информацию выше как справочную для решения задач ниже, которые уже реально покажут, владеете вы CSS-селекторами или нет.

Задачи

важность: 5

HTML-документ:

<input type="checkbox">
<input type="checkbox" checked>
<input type="text" id="message">

<h3 id="widget-title">Сообщения:</h3>
<ul id="messages">
  <li id="message-1">Сообщение 1</li>
  <li id="message-2">Сообщение 2</li>
  <li id="message-3" data-action="delete">Сообщение 3</li>
  <li id="message-4" data-action="edit do-not-delete">Сообщение 4</li>
  <li id="message-5" data-action="edit delete">Сообщение 5</li>
  <li><a href="#">...</a></li>
</ul>

<a href="http://site.com/list.zip">Ссылка на архив</a>
<a href="http://site.com/list.pdf">..И на PDF</a>

Задания:

  1. Выбрать input типа checkbox.
  2. Выбрать input типа checkbox, НЕ отмеченный.
  3. Найти все элементы с id=message или message-*.
  4. Найти все элементы с id=message-*.
  5. Найти все ссылки с расширением href="...zip".
  6. Найти все элементы с атрибутом data-action, содержащим delete в списке (через пробел).
  7. Найти все элементы, у которых ЕСТЬ атрибут data-action, но он НЕ содержит delete в списке (через пробел).
  8. Выбрать все чётные элементы списка #messages.
  9. Выбрать один элемент сразу за заголовком h3#widget-title на том же уровне вложенности.
  10. Выбрать все ссылки, следующие за заголовком h3#widget-title на том же уровне вложенности.
  11. Выбрать ссылку внутри последнего элемента списка #messages.

Открыть песочницу для задачи.

<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
</head>

<body>

  <input type="checkbox">
  <input type="checkbox" checked>
  <input type="text" id="message">

  <h3 id="widget-title">Сообщения:</h3>
  <ul id="messages">
    <li id="message-1">Сообщение 1</li>
    <li id="message-2">Сообщение 2</li>
    <li id="message-3" data-action="delete">Сообщение 3</li>
    <li id="message-4" data-action="edit do-not-delete">Сообщение 4</li>
    <li id="message-5" data-action="edit delete">Сообщение 5</li>
    <li><a href="#">...</a></li>
  </ul>


  <a href="http://site.com/list.zip">Ссылка на архив</a>
  <a href="http://site.com/list.pdf">..И на PDF</a>


  <script>
    // тестовая функция для селекторов
    // проверяет, чтобы элементов по селектору selector было ровно count
    function test(selector, count) {
      var elems = document.querySelectorAll(selector);
      var ok = (elems.length == count);

      if (!ok) alert(selector + ": " + elems.length + " != " + count);
    }

    // ------------- селекторы --------------

    // Выбрать input типа checkbox
    test('input[type="checkbox"]', 2);

    // Выбрать input типа checkbox, НЕ отмеченный
    test('input[type="checkbox"]:not(:checked)', 1);

    // Найти все элементы с id=message или message-*
    test('[id|="message"]', 6);

    // Найти все элементы с id=message-*
    test('[id^="message-"]', 5);

    // Найти все ссылки с расширением href="...zip"
    test('a[href$=".zip"]', 1);

    // Найти все элементы с data-action, содержащим delete в списке (через пробел)
    test('[data-action~="delete"]', 2);

    // Найти все элементы, у которых ЕСТЬ атрибут data-action,
    // но он НЕ содержащит delete в списке (через пробел)
    test('[data-action]:not([data-action~="delete"])', 1);

    // Выбрать все чётные элементы списка #messages
    test('#messages li:nth-child(2n)', 3);

    // Выбрать один элемент сразу за заголовком h3#widget-title
    // на том же уровне вложенности
    test('h3#widget-title + *', 1);

    // Выбрать все ссылки, следующие за заголовком h3#widget-title
    // на том же уровне вложенности
    test('h3#widget-title ~ a', 2);

    // Выбрать ссылку внутри последнего элемента списка #messages
    test('#messages li:last-child a', 1);
  </script>
</body>

</html>
важность: 4

Есть список UL/LI.

Текст вверху без отступа от списка.
<ul>
  <li>Маша</li>
  <li>Паша</li>
  <li>Даша</li>
  <li>Женя</li>
  <li>Саша</li>
  <li>Гоша</li>
</ul>
Текст внизу без отступа от списка.

Размеры шрифта и строки заданы стилем:

body {
  font: 14px/1.5 serif;
}

Сделайте, чтобы между элементами был вертикальный отступ.

  • Размер отступа: ровно 1 строка.
  • Нужно добавить только одно правило CSS с одним псевдоселектором, можно использовать CSS3.
  • Не должно быть лишних отступов сверху и снизу списка.

Результат:

Открыть песочницу для задачи.

Выбор элементов

Для выбора элементов, начиная с первого, можно использовать селектор nth-child.

Его вид: li:nth-child(n+2), т.к. n идёт от нуля, соответственно первым будет второй элемент (n=0), что нам и нужно.

Решение

Отступ, размером в одну строку, при line-height: 1.5 – это 1.5em.

Правило:

li:nth-child(n+2) {
  margin-top: 1.5em;
}

Ещё решение

Ещё один вариант селектора: li + li

li + li {
  margin-top: 1.5em;
}

Открыть решение в песочнице.

важность: 4

Есть список UL/LI.

Текст вверху без отступа от списка.
<ul>
  <li>Маша</li>
  <li>Паша</li>
  <li>Даша</li>
  <li>Женя</li>
  <li>Саша</li>
  <li>Гоша</li>
</ul>
Текст внизу без отступа от списка.

Размеры шрифта и строки заданы стилем:

body {
  font: 14px/1.5 serif;
}

Сделайте, чтобы между каждой парой элементов был вертикальный отступ.

  • Размер отступа: ровно 1 строка.
  • Нужно добавить только одно правило CSS, можно использовать CSS3.
  • Не должно быть лишних отступов сверху и снизу списка.

Результат:

Открыть песочницу для задачи.

Селектор

Для отступа между парами, то есть перед каждым нечётным элементом, можно использовать селектор nth-child.

Селектор будет li:nth-child(odd), к нему нужно ещё добавить отсечение первого элемента: li:nth-child(odd):not(:first-child).

Можно поступить и по-другому: li:nth-child(2n+3) выберет все элементы для n=0,1,2..., то есть 3-й, 5-й и далее, те же, что и предыдущий селектор. Немного менее очевидно, зато короче.

Правило

Отступ, размером в одну строку, при line-height: 1.5 – это 1.5em.

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

li:nth-child(odd):not(:first-child) {
  margin-top: 1.5em;
}

Получится так:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <style>
    body {
      font: 14px/1.5 serif;
    }

    ul {
      margin: 0;
    }

    li:nth-child(odd):not(:first-child) {
      margin-top: 1.5em;
    }
  </style>
</head>

<body>

  Текст вверху без отступа от списка.
  <ul>
    <li>Маша</li>
    <li>Паша</li>
    <li>Даша</li>
    <li>Женя</li>
    <li>Саша</li>
    <li>Гоша</li>
  </ul>
  Текст внизу без отступа от списка.

</body>

</html>

Открыть решение в песочнице.

Карта учебника

Комментарии

перед тем как писать…
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen…)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.