Хотите распределить элементы, привязавшись к их количеству, на одних стилях? Да запросто

в 13:25, , рубрики: css, Блог компании Uprock, Веб-разработка, метки:

Альтернативное название статьи – «почти :child-count(n)». Потому что именно так оно все и работает. На голом CSS и без каких-либо дата-атрибутов или чего-либо еще в верстке.

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

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

Хотите распределить элементы, привязавшись к их количеству, на одних стилях? Да запросто

Или, например, вы решили сделать радио, как в GTA 5. Вот такое:

image

И вам хочется расставить элементы по кругу, но вы не знаете, сколько их. Конечно, для разных случаев – нужно задать разный 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 строк.

Я не знаю, как еще это можно использовать, но в этом что-то явно есть, что-то большое и интересное. Если у вас есть идеи — с удовольствием добавлю их примеры сюда, в конец поста.

Автор: Jabher

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js