Вам когда-нибудь хотелось прямо в CSS коде посчитать, в меню 4 элемента или 10? Для четырех задать им ширину по 25%, а если набралось десять — прижать их друг к другу и уменьшить отступы?
Как выяснилось, CSS умеет работать с разным количеством элементов, позволяя избавиться от головных болей и лишнего кода на js.

Респонсив дизайн обычно ассоцируется с одной переменной — пространством. Когда мы тестируем респонсив сетки, мы берем какой-то объем контента и смотрим, сколько места он займет, как это выглядит и куда он влазит, а куда нет. То есть, контент считается константой, а пространство — переменной.
Media query являются основой респонсив дизайна, так как позволяют размечать гра��ицы, переходя которые одна сетка меняется на другую. Однако, на расстановку элементов и пространство вокруг них может влиять не только размер экрана, но и собственно сам контент.
Точно так же, как посетители сайта могут пользоваться множеством устройств с разными размерами экранов, ваши контент-менеджеры и редакторы могут добавлять или удалять контент. У них даже для этого целые CMS есть. Именно поэтому дизайны страниц в фотошопе потеряли свою актуальность — в них всегда фиксированная ширина экрана и всегда фиксированный контент.
В этой статье я опишу технику создания CSS, лишенного проблем с количеством (подсчётом) элементов. Достигается это за счёт специально оформленных селекторов. Я буду применять их в контесте решения классической проблемы: как разделить вёрстку элементов в горизонтальном меню сайта, когда в нём мало и много элементов. Например, как добиться того, что при 6 и более пунктах меню элементы имели стиль display: inline-block, а при меньшем количестве — display: table-cell.
Я не буду использовать шаблонизацию или js, мне даже не понадобятся прописывать классы у элементов меню. Техника использования только CSS селекторов соответствует принципу разделения интересов, согласно которому, контент (HTML) и отображение (CSS) имеют ясно обозначенные роли. Разметка — это работа CSS и, по возможности, одного только CSS.

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

Чтобы узнать количество элементов, их нужно посчитать.
Селектор :only-child срабатывает на элементах, которых всегда одна штука. По сути, это позволяет нам «применить стили ко всем дочерним элементам конкретного элемента, если суммарно их ровно 1». Это единственн��й простой селектор, который можно описать как «подсчитывающий» (кроме, конечно, похожего на него :only-of-type).
В описанном ниже примере я использую :only-of-type, чтобы применить стили ко всем кнопкам, которые являются единственными чилдами-кнопками среди их соседей.
Важно понимать, что очередность строк здесь имеет ключевое значение. Кроме этого, полезно видеть этот код с точки зрения селекта «меньше-чем-два»:

Аналогично, мы теперь можем сделать селект «больше-чем-один», используя отрицание.

Применять стили, основываясь на «больше-чем-один» и «меньше-чем-два» это ловкий трюк, но нам нужен более гибкий инструмент, позволяющий оперировать с любым числом. Мы хотим использовать селекты “больше или равно N” для любого N. Кроме этого, хочется иметь селект «ровно 745» или «суммарно ровно 6».
Для этого у нас есть селектор :nth-last-child(n), в который мы параметром передаем любое число. Он позволяет отсчитать столько-то элементов назад от конца списка. Например, :nth-last-child(6) выберет элемент, который является шестым с конца среди своих соседних элементов.
Всё становится интереснее, когда мы совмещаем :nth-last-child(6) и :first-child, в результате получая все элементы, которые являются шестыми с конца и при этом первыми с начала.
Если такой элемент существует, это будет значить, что у нас ровно 6 элементов. Таким образом, я написал CSS код, который может сказать, сколько элементов я вижу перед собой.

Осталось теперь воспользоваться этим одновременно «шестым с конца и первым с начала» элементом, чтобы еще и поселектать все остальные 5 элементов. Для этого я воспользуюсь общим соседним комбинатором.

Если вы не знакомы с этим комбинатором, то объясняю, ~ li в селекте li:nth-last-child(6):first-child ~ li значит «любой li, который идёт после li:nth-last-child(6):first-child». В нашем случае, элементы будут иметь зеленый цвет шрифта, если их ровно 6 штук.
Селектать фиксированное количество, будь то 6, 19 или 653 — не очень-то полезно, так как подобная необходимость очень редка. Это как в media queries — не очень удобно использовать фиксрованную ширину вместо min-width или max-width:
В навигационном меню я хочу переключать стили с границей, основанной на количестве элементов, например, поменять нужные стили, если у меня 6 и больше элементов (а не ровно шесть).
Вопрос в том, как сделать такой селектор? и это вопрос смещений.
Селектор :nth-child() может принимать параметром не только число, но и формулу “n + [число]”. Например, :nth-child(n+6) будет применён ко всем элементам, начиная с шестого.

Хоть этот селектор и является мощным инструментом сам по себе, он не позволяет оперировать с количеством. Он применится не тогда, когда у нас больше шести элементов, а просто к элементам, у которых номер больше пяти.
Чтобы обойти эту проблему, нам нужно создать такой селект, который выделит все элементы, кроме последних пяти. Используя обратный к :nth-child(n+6) селект :nth-last-child(n+6) мы сможем выделить все элементы «от с шестого с конца и до самого первого с начала».
Такой селект отсекает последние пять элементов от набора любой длины, что означает, что если у вас меньше шести элементов, то в селект ничего и не попадет.

Если же в списке шесть или больше элементов, нам остаётся прибавить к селекту оставшиеся пять элементов. Это легко — если элементов больше шести, то срабатывает условие nth-last-child(n+6), и комбинируя его с "~" мы сможем поселектать все нужные нам элементы.

Такая вот короткая запись и является решением нашей проблемы:
Разумеется, тут может быть любое положительное целое число, даже 653,279.
Как и в предыдущем примере с :only-of-type, селектор можно использовать в обе стороны, и как «больше или равно N» и как «меньше или равно N». Какой вариант вы будете использовать? Зависит от того, какую сетку вы будете считать основной.
В случае «Меньше или N» мы берем n с минусом и добавляем условие :first-child.
В результате, использование "-" меняет направление селекта: вместо того, чтобы считать от начала и до шести, отсчёт будет идти от конца до шести. В обоих случаях, шестой элемент будет включён в селект.
Заметьте, что в предыдущих примерах я использовал :nth-child() и :nth-last-child(), а не :nth-of-type() с :nth-last-of-type(). Так как я селектал теги <li> и правильными потомками <ul> могут быть только они, :last-child() и :last-of-type() оказываются одинаково результативны.
Обе группы селектов :nth-child() и :nth-of-type() имеют свои преимущества, исходя из того, чего вы хотите добиться. Так как :nth-child() не привязан к конкретному тегу, описанную выше технику можно применять к смешанным дочерним элементам:
(Обратите внимание, чтобы не привязываться к тегам, я использую универсальный селектор. :last-child(n+3) ~ *. В этом случае, он обозначает «любой элемент любого тега, следующий за :last-child(n+3).»
С другой стороны, преимущество :nth-last-of-type() в том, что вы можете селектать элементы с одним тегом среди множества других соседних. Например, можно посчитать количество тегов <p>, несмотря на то, что они в одной куче с <div> и <blockquote>.
Все используемые в статье селекторы CSS2.1 и CSS3 поддерживаются в Internet Explorer 9 и выше, а так же все современные мобильные и десктопные браузеры.
Internet Explorer 8 неплохо поддерживает большинство селектов, но задуматься о полифилле на js не помешает. В качестве альтернативы, можно расставить специальные классы для старых версий IE и приписать их к своим селекторам. В случае с навигационным меню, код будет выглядеть примерно так:
Предположим, наше навигационное меню принадлежит сайту с CMS. В зависимости от того, кто его наполняет и использует, в меню будет разное количество элементов. Кто-то старается не усложнять и ему хватает «Home» и «About», а кто-то хочет запихать в меню всё, что есть у него на сайте.
Создавая стили, учитывающие разные сетки для разного количества элементов в меню, мы делаем вёрстку более стройной и подходящей для самых разных форматов — какой бы ни был контент, у нас всё учтено.

Поздравляю, теперь в вашем CSS арсенале есть умение оперировать количеством элементов.
Респонсив дизайн решает одну важную проблему: он позволяет комфортно расположить один и тот же контент на множестве разных устройств. Было бы неприемлемо видеть разный контент только потому, что у тебя экран неправильного размера. Так же и неприемлемо диктовать дизайнеру, под сколько пунктов меню должен быть заточен сайт. «Не рисуй так, у нас вся сетка поедет, если будет столько элементов».
Какую форму примет контент и сколько его будет в конкретный момент времени — никто не знает. И мы не можем всегда полагаться на скрипты или обрезание текста, это неправильно. Правильно — быть независимым от количества контента и создавать для этого нужные механизмы и инструменты. Количественные селекторы это просто одна из идей для этого.
Веб дизайн это всегда изменчивость, гибкость и неопределенность. В нём скорее не знаешь, чем знаешь. Он уникален тем, что представляет с собой вид визуального дизайна, который не определяет формы, а предвидит, какие формы может что-то принять. Для кого-то это неприемлемо и непонятно, но для вас и меня это вызов и удовольствие.
Как выяснилось, CSS умеет работать с разным количеством элементов, позволяя избавиться от головных болей и лишнего кода на js.

Динамичный контент
Респонсив дизайн обычно ассоцируется с одной переменной — пространством. Когда мы тестируем респонсив сетки, мы берем какой-то объем контента и смотрим, сколько места он займет, как это выглядит и куда он влазит, а куда нет. То есть, контент считается константой, а пространство — переменной.
Media query являются основой респонсив дизайна, так как позволяют размечать гра��ицы, переходя которые одна сетка меняется на другую. Однако, на расстановку элементов и пространство вокруг них может влиять не только размер экрана, но и собственно сам контент.
Точно так же, как посетители сайта могут пользоваться множеством устройств с разными размерами экранов, ваши контент-менеджеры и редакторы могут добавлять или удалять контент. У них даже для этого целые CMS есть. Именно поэтому дизайны страниц в фотошопе потеряли свою актуальность — в них всегда фиксированная ширина экрана и всегда фиксированный контент.
В этой статье я опишу технику создания CSS, лишенного проблем с количеством (подсчётом) элементов. Достигается это за счёт специально оформленных селекторов. Я буду применять их в контесте решения классической проблемы: как разделить вёрстку элементов в горизонтальном меню сайта, когда в нём мало и много элементов. Например, как добиться того, что при 6 и более пунктах меню элементы имели стиль display: inline-block, а при меньшем количестве — display: table-cell.
Я не буду использовать шаблонизацию или js, мне даже не понадобятся прописывать классы у элементов меню. Техника использования только CSS селекторов соответствует принципу разделения интересов, согласно которому, контент (HTML) и отображение (CSS) имеют ясно обозначенные роли. Разметка — это работа CSS и, по возможности, одного только CSS.

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

Счёт
Чтобы узнать количество элементов, их нужно посчитать.
Прим. пер
CSS не даёт явного «подсчитывающего API», но мы можем решить эту проблему в обход, комбинируя селекторы нужным образом.Капитан
Считаем до одного
Селектор :only-child срабатывает на элементах, которых всегда одна штука. По сути, это позволяет нам «применить стили ко всем дочерним элементам конкретного элемента, если суммарно их ровно 1». Это единственн��й простой селектор, который можно описать как «подсчитывающий» (кроме, конечно, похожего на него :only-of-type).
В описанном ниже примере я использую :only-of-type, чтобы применить стили ко всем кнопкам, которые являются единственными чилдами-кнопками среди их соседей.
button {
font-size: 1.25em;
}
button:only-of-type {
font-size: 2em;
}
Важно понимать, что очередность строк здесь имеет ключевое значение. Кроме этого, полезно видеть этот код с точки зрения селекта «меньше-чем-два»:

Аналогично, мы теперь можем сделать селект «больше-чем-один», используя отрицание.
/* "больше-чем-один" будут иметь уменьшенный размер шрифта */
button {
font-size: 2em;
}
button:not(:only-of-type) {
font-size: 1.25em;
}

N элементов
Применять стили, основываясь на «больше-чем-один» и «меньше-чем-два» это ловкий трюк, но нам нужен более гибкий инструмент, позволяющий оперировать с любым числом. Мы хотим использовать селекты “больше или равно N” для любого N. Кроме этого, хочется иметь селект «ровно 745» или «суммарно ровно 6».
Для этого у нас есть селектор :nth-last-child(n), в который мы параметром передаем любое число. Он позволяет отсчитать столько-то элементов назад от конца списка. Например, :nth-last-child(6) выберет элемент, который является шестым с конца среди своих соседних элементов.
Всё становится интереснее, когда мы совмещаем :nth-last-child(6) и :first-child, в результате получая все элементы, которые являются шестыми с конца и при этом первыми с начала.
li:nth-last-child(6):first-child {
/* зеленый кальмар */
}
Если такой элемент существует, это будет значить, что у нас ровно 6 элементов. Таким образом, я написал CSS код, который может сказать, сколько элементов я вижу перед собой.

Осталось теперь воспользоваться этим одновременно «шестым с конца и первым с начала» элементом, чтобы еще и поселектать все остальные 5 элементов. Для этого я воспользуюсь общим соседним комбинатором.

Если вы не знакомы с этим комбинатором, то объясняю, ~ li в селекте li:nth-last-child(6):first-child ~ li значит «любой li, который идёт после li:nth-last-child(6):first-child». В нашем случае, элементы будут иметь зеленый цвет шрифта, если их ровно 6 штук.
li:nth-last-child(6):first-child,
li:nth-last-child(6):first-child ~ li {
color: green;
}
Больше или равно 6
Селектать фиксированное количество, будь то 6, 19 или 653 — не очень-то полезно, так как подобная необходимость очень редка. Это как в media queries — не очень удобно использовать фиксрованную ширину вместо min-width или max-width:
@media screen and (width: 500px) {
/* стили для ширины вьюпорта ровно в 500px */
}
В навигационном меню я хочу переключать стили с границей, основанной на количестве элементов, например, поменять нужные стили, если у меня 6 и больше элементов (а не ровно шесть).
Вопрос в том, как сделать такой селектор? и это вопрос смещений.
Параметр n+6
Селектор :nth-child() может принимать параметром не только число, но и формулу “n + [число]”. Например, :nth-child(n+6) будет применён ко всем элементам, начиная с шестого.

Хоть этот селектор и является мощным инструментом сам по себе, он не позволяет оперировать с количеством. Он применится не тогда, когда у нас больше шести элементов, а просто к элементам, у которых номер больше пяти.
Чтобы обойти эту проблему, нам нужно создать такой селект, который выделит все элементы, кроме последних пяти. Используя обратный к :nth-child(n+6) селект :nth-last-child(n+6) мы сможем выделить все элементы «от с шестого с конца и до самого первого с начала».
li:nth-last-child(n+6) {
/* здесь стили */
}
Такой селект отсекает последние пять элементов от набора любой длины, что означает, что если у вас меньше шести элементов, то в селект ничего и не попадет.

Если же в списке шесть или больше элементов, нам остаётся прибавить к селекту оставшиеся пять элементов. Это легко — если элементов больше шести, то срабатывает условие nth-last-child(n+6), и комбинируя его с "~" мы сможем поселектать все нужные нам элементы.

Такая вот короткая запись и является решением нашей проблемы:
li:nth-last-child(n+6),
li:nth-last-child(n+6) ~ li {
/* здесь стили */
}
Разумеется, тут может быть любое положительное целое число, даже 653,279.
Меньше или N
Как и в предыдущем примере с :only-of-type, селектор можно использовать в обе стороны, и как «больше или равно N» и как «меньше или равно N». Какой вариант вы будете использовать? Зависит от того, какую сетку вы будете считать основной.
В случае «Меньше или N» мы берем n с минусом и добавляем условие :first-child.
li:nth-last-child(-n+6):first-child,
li:nth-last-child(-n+6):first-child ~ li {
/* здесь стили */
}
В результате, использование "-" меняет направление селекта: вместо того, чтобы считать от начала и до шести, отсчёт будет идти от конца до шести. В обоих случаях, шестой элемент будет включён в селект.
nth-child против nth-of-type
Заметьте, что в предыдущих примерах я использовал :nth-child() и :nth-last-child(), а не :nth-of-type() с :nth-last-of-type(). Так как я селектал теги <li> и правильными потомками <ul> могут быть только они, :last-child() и :last-of-type() оказываются одинаково результативны.
Обе группы селектов :nth-child() и :nth-of-type() имеют свои преимущества, исходя из того, чего вы хотите добиться. Так как :nth-child() не привязан к конкретному тегу, описанную выше технику можно применять к смешанным дочерним элементам:
<div class="container">
<p>...</p>
<p>...</p>
<blockquote>...</blockquote>
<figure>...</figure>
<p>...</p>
<p>...</p>
</div>
.container > :nth-last-child(n+3),
.container > :nth-last-child(n+3) ~ * {
/* здесь стили */
}
(Обратите внимание, чтобы не привязываться к тегам, я использую универсальный селектор. :last-child(n+3) ~ *. В этом случае, он обозначает «любой элемент любого тега, следующий за :last-child(n+3).»
С другой стороны, преимущество :nth-last-of-type() в том, что вы можете селектать элементы с одним тегом среди множества других соседних. Например, можно посчитать количество тегов <p>, несмотря на то, что они в одной куче с <div> и <blockquote>.
<div class="container">
<div>...</div>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<p>...</p>
<blockquote>...</blockquote>
</div>
p:nth-last-of-type(n+6),
p:nth-last-of-type(n+6) ~ p {
/* здесь стили */
}
Поддержка браузерами
Все используемые в статье селекторы CSS2.1 и CSS3 поддерживаются в Internet Explorer 9 и выше, а так же все современные мобильные и десктопные браузеры.
Internet Explorer 8 неплохо поддерживает большинство селектов, но задуматься о полифилле на js не помешает. В качестве альтернативы, можно расставить специальные классы для старых версий IE и приписать их к своим селекторам. В случае с навигационным меню, код будет выглядеть примерно так:
nav li:nth-last-child(n+6),
nav li:nth-last-child(n+6) ~ li,
.lt-ie9 nav li {
display: inline-block;
/* и т.д. */
}
В реальном мире
Предположим, наше навигационное меню принадлежит сайту с CMS. В зависимости от того, кто его наполняет и использует, в меню будет разное количество элементов. Кто-то старается не усложнять и ему хватает «Home» и «About», а кто-то хочет запихать в меню всё, что есть у него на сайте.
Создавая стили, учитывающие разные сетки для разного количества элементов в меню, мы делаем вёрстку более стройной и подходящей для самых разных форматов — какой бы ни был контент, у нас всё учтено.

Поздравляю, теперь в вашем CSS арсенале есть умение оперировать количеством элементов.
Контент-независимый дизайн
Респонсив дизайн решает одну важную проблему: он позволяет комфортно расположить один и тот же контент на множестве разных устройств. Было бы неприемлемо видеть разный контент только потому, что у тебя экран неправильного размера. Так же и неприемлемо диктовать дизайнеру, под сколько пунктов меню должен быть заточен сайт. «Не рисуй так, у нас вся сетка поедет, если будет столько элементов».
Какую форму примет контент и сколько его будет в конкретный момент времени — никто не знает. И мы не можем всегда полагаться на скрипты или обрезание текста, это неправильно. Правильно — быть независимым от количества контента и создавать для этого нужные механизмы и инструменты. Количественные селекторы это просто одна из идей для этого.
Веб дизайн это всегда изменчивость, гибкость и неопределенность. В нём скорее не знаешь, чем знаешь. Он уникален тем, что представляет с собой вид визуального дизайна, который не определяет формы, а предвидит, какие формы может что-то принять. Для кого-то это неприемлемо и непонятно, но для вас и меня это вызов и удовольствие.
