Решено с помощью CSS! Логическая стилизация на основе числа элементов

Перевод статьи Solved with CSS! Logical Styling Based on the Number of Given Elements с сайта css-tricks.com для CSS-live.ru, автор — Юна Кравец

Эта статья третья из серии про мощь CSS.

Все статьи серии:

А вы знали, что CSS — Тьюринг-полный? А что его можно использовать для вполне серьёзной логической стилизации? Можно-можно! И не нужно закладывать логику для стилевых правил в JavaScript, или навешивать скриптом классы, для которых вы задаете стили. Во многих случаях CSS сам справится с этим. Я до сих пор ежедневно открываю новые CSS-трюки, и этим CSS нравится мне всё больше и больше.

В этом году, я начала работать в издательстве Bustle Digital Group. В СМИ, как и во множестве продуктов,  команда разработчиков выстраивает платформу, которая должна подходить для всех практических задач. Наша CMS даёт возможность авторам и редакторам создавать статьи, а также править страницы и управлять вставкой рекламы.

В отличии от работы со статическим сайтом, команда разработчиков не может полностью контролировать данные, поступающие от пользователя, так что удобства для пользователей не достичь без дизайнерских решений и правил управления. Некоторые из таких сценариев, с которыми мы столкнулись в мире цифровых СМИ, подкинули нам настоящие головоломки по части пользовательского интерфейса, которые и вдохновили меня присмотреться к способам их решения на CSS.

Итак, взглянем на некоторые примеры!

Пример 1: бинарные состояния

Часто забытый и весьма полезный селектор — псевдоселектор :empty. Он позволяет оформлять элементы, отталкиваясь от того, есть ли в них контент или нет. Привет пустым состояниям! Пустые состояния — отличный способ «достучаться» до пользователя, «очеловечив» ваше приложение, и это можно сделать прямо из CSS.

В этом примере у нас есть какой-то список от пользователя. Это могут быть статьи, которые пользователь опубликовал (как автор), или сохранил в закладках (как редактор). Применений тут масса. Вместо JavaScript можно использовать псевдоэлементы для вставки изображения, стилей и текста:

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

И решение здесь — всего лишь три строчки кода:

div:empty:after {
  content: 'Ой, ничего';
}

Можете также добавить псевдоэлемент :before для вставки изображения или любого нужного вам контента. Как альтернатива, можно взять псевдоселектор :not вместе с :empty, сделать правило :not(:empty) и оформить им все непустые элементы, то есть элементы с контентом.

See the Pen Empty States by Максим (@psywalker) on CodePen.

Примечание: этот пример существует только в целях демонстрации этой техники. Нежелательно класть контент в псевдоэлементы по соображениям доступности. Можно использовать тот же прием выбора пустых (:empty) или непустых (:not(:empty)) элементов, чтобы применить к дочерним элементам более доступные для скринридеров стили.

Продвинутый выбор по количеству

Этот пример был неплохой разминкой, но на CSS можно не только проверять, если ли дочерние элементы, но и решать более сложные задачи. В этом нам пригодится псевдоселектор :nth-child! На CSS-Tricks есть отличный инструмент, помогающий тестировать и играть с выборкой :nth-child, и вскоре вы узнаете, что он действительно может пригодиться.

Но прежде давайте выясним, как именно это работает?

Главное в этом коде — вот что, где div обозначает любой элемент, соседей которого мы будем считать, а x — число соседей, при котором стиль должен меняться:

div:first-child:nth-last-child(n + x),
div:first-child:nth-last-child(n + x) ~ div

Использование :nth-last-child вместо :nth-child для выбора позволяет нам начать с конца последовательности, а не сначала. При выборе :nth-last-child(n + x) мы выбираем значение с номером x, начиная с конца. Если x = 3, то это выглядело бы так:

Иллюстрация того, как :nth-last-child(3) выбирает третий элемент с конца списка.

Итак, если мы хотим посчитать значения n + 3, мы выбираем все элементы, которые являются 3-м или более чем 3-м с конца. Начиная с n=0 (что будет значить 0 + 3), а 4-й элемент будет первым с конца после третьего. Это выглядит так:

Иллюстрация того, как :nth-last-child(n+3) выбирает все элементы, соответствующие 3 или более 3 с конца.

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

Ой. Сейчас у нас выбран только первый элемент, а нам нужны все элементы. К счастью, для этого можно использовать очень удобный селектор соседних элементов (~).

Если изменить наш последний пример на :first-child:last-child(n + 3) ~ *, то он выберет все элементы, кроме первого, почти как нам нужно.

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

Сочетание обоих предыдущих примеров выберет все элементы в списке.

Пример 2: форматирование списка

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

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

Иллюстрации вертикального неупорядоченного списка (слева) и горизонтального списка, разделённого точкой с запятой (справа)

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

/* 5 или более элементов отображаются друг за другом */
li:first-child:nth-last-child(n + 5),
li:first-child:nth-last-child(n + 5) ~ li {
  display: inline;
}

/* Добавляем точку с запятой после каждого элемента кроме последнего */
li:first-child:nth-last-child(n + 5) ~ li::before {
  content: ';';
  margin: 0 0.5em 0 -0.75em;
}

:nth-first-child:nth-last-child(n + 5) позволяет сообщить: «начни с первого дочернего элемента и примени стили к нему и к элементам, следующим за ним, если их пять и более». Запутанно? Нуу, это работает.

li:first-child:nth-last-child(n + 5) выбирает первый элемент списка, а li:first-child:nth-last-child(n + 5) ~ li — каждый элемент, следующий за первым.

See the Pen RYWoZY by Максим (@psywalker) on CodePen.

Пример 3: карусель с условием

Давайте с помощью этой техники сделаем карусель отзывчивой. На большом экране она должна центрироваться посередине страницы, когда она содержит три элемента. Но когда элементов достаточно, чтобы заполнить экран по горизонтали, то выровним её по левому краю, чтобы пользователь мог листать (свайпить) её.

Иллюстрация карусели с тремя элементами (слева) и с четырьмя и более (справа)

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

Мы можем воспользоваться тем же самым приёмом, что в предыдущем примере, но нам ещё понадобится один лишь first-child, чтобы найти и отобразить в интерфейсе div со стрелкой. HTML выглядел бы так:

<ul>
  <li>
    <div class="box">1</div>
  </li>
  <li>
    <div class="box">2</div>
  </li>
  ...
  <button class="arrow">——></button>
</ul>

Пустые элементы в DOM — не идельно, но смотрите. Это по-прежнему умный хак. Мы применим к кнопке .arrow visibility: hidden, сделав её невидимой для DOM и скринридеров, если условия не применяются (если элементов четыре или более). В противном случае мы отобразим её с помощью display: block, применим к ней стили и нужное позиционирование.

li:first-child:nth-last-child(n + 5) ~ .arrow {
  display: block;
  position: sticky;
  ...
}

See the Pen Box Alignment by Максим (@psywalker) on CodePen.


Больше информации!

При исследовании данного материала я нашла отличную статью Хейдона Пикеринга по этой теме, названную «Количественные выражения», и ещё примеры Лии Веру! В комментариях к статье Хейдона Пол Айриш отмечает, что это более медленный способ выборки элементов, так что используйте его с осторожностью.

P.S. Это тоже может быть интересно:

7

Комментарии

  1. Вадим Макеев

    Ребята, колоризация — это раскрашивание. Зачем придумывать кальки, если есть слова?

    1. Максим Усачев (Автор записи)

      Согласен, исправили!

  2. Николай

    li:first-child:nth-last-child(n + 5),
    li:first-child:nth-last-child(n + 5) ~ li {
    display: inline;
    }
    Зачем здесь first-child? Без него точно также работает пример с форматированием списка.

    1. SelenIT

      SelenIT

      Похоже, что так! Возможно, он механически остался от другой задачи — «выбрать элементы, если их ровно n» (как у Хейдона), а не «n и больше». Браво за внимательность!

  3. Qwerty Wasd

    Все не читал, но первое что бросилось в глаза — :first-child:nth-last-child(n+3)~*{}

    Зачем так городить, вот же .item:first-child~*{}

    1. SelenIT

      SelenIT

      Это выберет все элементы, кроме первого (если у этого первого есть класс .item) независимо от их количества. То есть теряется главная идея подхода — по-разному оформлять элементы, когда их много и когда их мало. Выше выяснили, что можно отбросить как раз :first-child, но без :nth-last-child не обойтись.

  4. Алексей

    Интересные идеи!

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Ваш E-mail не будет опубликован

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