Альтернативное название статьи – «почти :child-count(n)». Потому что именно так оно все и работает. На голом CSS и без каких-либо дата-атрибутов или чего-либо еще в верстке.
Представьте, что у вас есть, например, какая-нибудь лента новостей. Неважно, какая. Главное, что вы не знаете, сколько в ней будет элементов, и как их расставить так, чтобы было симметрично. И хочется сделать что-то бесполезное, но красивое: например, расставить все в две колонки, а некоторые блоки вставить во всю ширину. Каждый третий, или каждый пятый.
Конечно же, если у вас четыре элемента, а третий вы сделали во всю ширину – последний будет свешиваться в конце. Поэтому нужно применять такую красивую и бесполезную вещь только в том случае, если количество элементов кратно трем. А если их нечетное число(но не кратное трем) — нужно делать, например, последний элемент во всю шинину.
Вот так, например:
![](https://habrastorage.org/r/w1560/getpro/habr/post_images/d60/c5d/72c/d60c5d72c9df2d360863745352c1f4e2.png)
Или, например, вы решили сделать радио, как в GTA 5. Вот такое:
![image](https://habrastorage.org/r/w780q1/getpro/habr/post_images/2c8/a00/e85/2c8a00e85e6aaf67f1e36fa16e4c7e22.jpg)
И вам хочется расставить элементы по кругу, но вы не знаете, сколько их. Конечно, для разных случаев – нужно задать разный transform: rotate(). Ну или, при желании, можно расположить все через left и top, опираясь на sin и cos. Но все равно, вам нужно знать, сколько элементов у вас есть.
В проекте, где нужно было реализовать именно такую фичу, я сначала сделал все через простейший js:
И в scss работал непосредственно с
Я надеюсь, этот код понятен всем. Если нет — он разворачивается в конструкции вида .parent[children=2] > :nth-child(0) {transform: rotate(0deg)} и далее по циклу
Можно было обернуть вызов этой функции в MutationObserver, чтобы следить за количеством потомков, но все равно было ощущение неправильности данного решения – это была просто стилистика, которая никак не относилась к основной логике.
Наверное, у каждого человека, который занимается стилями достаточно долгое время, где-то на уровне интуиции появляется детектор «можно ли это сделать на одних стилях».
В итоге, почти сутки фоном шел поиск решения, как можно определить количество элементов. Решений оказалось даже два, правда очень похожих.
Логика была достаточно простой, технически мы можем обращаться к практически любому элементу вперед (через ~ и +), но чтобы определить количество элементов в списке, нужно иметь возможность пройти вперед, сосчитать все элементы и вернуться назад.
«Обратное» движение возможно при помощи всего двух атрибутов — nth-last-of-type и nth-last-child.
При этом фактически это движение выполняется от последнего элемента, так что в итоге нужно пройти все элементы начиная от первого и заканчивая последним через nth-last-child и обнаружить «финальность» элемента через first-child
В итоге первой версией селектора стало
что позволяло обратиться к первому элементу в случае, если он является 20-м с конца (то есть у его родителя всего 20 детей).
Перейти к любому следующему элементу можно было примерно так:
Это давало читаемость, но все равно выглядело несколько громоздким.
Самое время было вспомнить, что :first-child является частным случаем :nth-child(1), в итоге мы получали селектор
Что дало возможность двигаться вправо и влево любыми способами, явно «цепляясь» за начало и конец списка детей.
В реальном scss это превратилось в подобную несколько монструозную конструкцию
Вот примеры того, как это работает:
Пример с различной шириной блоков в зависимости от их количества
Пример с равномерным распределением элементов по кругу
Этот примеры, пожалуй, несколько надуманы, но в действительности они показывают реальные возможности подобного «точечного» условного обращения к элементам.
Из минусов я мог бы назвать только большой размер выходного css файла: для конструкции в 20 элементов, в каждом селекторе которой было 14 строк из-за вендорных префиксов длина итогового стиля составила около 2000 строк.
Я не знаю, как еще это можно использовать, но в этом что-то явно есть, что-то большое и интересное. Если у вас есть идеи — с удовольствием добавлю их примеры сюда, в конец поста.
Представьте, что у вас есть, например, какая-нибудь лента новостей. Неважно, какая. Главное, что вы не знаете, сколько в ней будет элементов, и как их расставить так, чтобы было симметрично. И хочется сделать что-то бесполезное, но красивое: например, расставить все в две колонки, а некоторые блоки вставить во всю ширину. Каждый третий, или каждый пятый.
Конечно же, если у вас четыре элемента, а третий вы сделали во всю ширину – последний будет свешиваться в конце. Поэтому нужно применять такую красивую и бесполезную вещь только в том случае, если количество элементов кратно трем. А если их нечетное число(но не кратное трем) — нужно делать, например, последний элемент во всю шинину.
Вот так, например:
![](https://habrastorage.org/getpro/habr/post_images/d60/c5d/72c/d60c5d72c9df2d360863745352c1f4e2.png)
Или, например, вы решили сделать радио, как в GTA 5. Вот такое:
![image](https://habrastorage.org/getpro/habr/post_images/2c8/a00/e85/2c8a00e85e6aaf67f1e36fa16e4c7e22.jpg)
И вам хочется расставить элементы по кругу, но вы не знаете, сколько их. Конечно, для разных случаев – нужно задать разный transform: rotate(). Ну или, при желании, можно расположить все через left и top, опираясь на sin и cos. Но все равно, вам нужно знать, сколько элементов у вас есть.
В проекте, где нужно было реализовать именно такую фичу, я сначала сделал все через простейший js:
function countElementChildren(element) {
$(element).attr('children', $(element).children().length)
}
И в scss работал непосредственно с
@for $i from 1 through 20 {
.parent[children="#{$i}"] {
@for $j from 1 through $i {
& > :nth-child(#{$j}) {
transform: rotate(360deg * (($j - 1) / ($i - 1)));
}
}
}
}
Я надеюсь, этот код понятен всем. Если нет — он разворачивается в конструкции вида .parent[children=2] > :nth-child(0) {transform: rotate(0deg)} и далее по циклу
Можно было обернуть вызов этой функции в MutationObserver, чтобы следить за количеством потомков, но все равно было ощущение неправильности данного решения – это была просто стилистика, которая никак не относилась к основной логике.
Наверное, у каждого человека, который занимается стилями достаточно долгое время, где-то на уровне интуиции появляется детектор «можно ли это сделать на одних стилях».
В итоге, почти сутки фоном шел поиск решения, как можно определить количество элементов. Решений оказалось даже два, правда очень похожих.
Логика была достаточно простой, технически мы можем обращаться к практически любому элементу вперед (через ~ и +), но чтобы определить количество элементов в списке, нужно иметь возможность пройти вперед, сосчитать все элементы и вернуться назад.
«Обратное» движение возможно при помощи всего двух атрибутов — nth-last-of-type и nth-last-child.
При этом фактически это движение выполняется от последнего элемента, так что в итоге нужно пройти все элементы начиная от первого и заканчивая последним через nth-last-child и обнаружить «финальность» элемента через first-child
В итоге первой версией селектора стало
:nth-last-child(20):first-child {
}
что позволяло обратиться к первому элементу в случае, если он является 20-м с конца (то есть у его родителя всего 20 детей).
Перейти к любому следующему элементу можно было примерно так:
:nth-last-child(20):first-child ~ :nth-child(10) {
}
Это давало читаемость, но все равно выглядело несколько громоздким.
Самое время было вспомнить, что :first-child является частным случаем :nth-child(1), в итоге мы получали селектор
:nth-child(1):nth-last-child(20) {
}
Что дало возможность двигаться вправо и влево любыми способами, явно «цепляясь» за начало и конец списка детей.
В реальном scss это превратилось в подобную несколько монструозную конструкцию
@for $i from 1 through 20 {
@for $j from 1 through $i {
.child:nth-child(#{$j}):nth-last-child(#{$i - $j}) {
@include transform(rotate(360deg * (($j - 1) / ($i - 1))))
}
}
}
Вот примеры того, как это работает:
Пример с различной шириной блоков в зависимости от их количества
Пример с равномерным распределением элементов по кругу
Этот примеры, пожалуй, несколько надуманы, но в действительности они показывают реальные возможности подобного «точечного» условного обращения к элементам.
Из минусов я мог бы назвать только большой размер выходного css файла: для конструкции в 20 элементов, в каждом селекторе которой было 14 строк из-за вендорных префиксов длина итогового стиля составила около 2000 строк.
Я не знаю, как еще это можно использовать, но в этом что-то явно есть, что-то большое и интересное. Если у вас есть идеи — с удовольствием добавлю их примеры сюда, в конец поста.