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

    Альтернативное название статьи – «почти :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 строк.

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

    Comments 19

      +13
        +2
        Я долго искал что-либо подобное, и не смог найти. Да и никто из верстальщиков и фронтэнд-разработчиков, кого я знаю, не слышал о таком решении — специально уточнял, чтобы не повторяться.
        Возможно, дело в том, что то решение подавалось как узкоспециализированное. В данном же случае идет указание на целевой элемент с условием определенного количества детей, а не просто как решение для автоматического задания ширины для определенного количества элементов в списке.
          0
          С помощью nth-child можно вообще разные вещи творить. Хотя в приведенной ссылке речь идет не о выравнивании элементов исходя из их числа
          +2
          Так почему ты об этом решении до сих пор не рассказал?
            0
            Jabher: Бро, ты в любом случае молодец, и все кто увидел это решение впервые крепко жмут тебе руку )
          0
          Я так понимаю, что если у нас неизвестное заранее количество элементов, то метод не работает? Тогда какой в нём смысл?
            0
            Нет, это такое же обращение как и любой другой nth-child.
            в примере с блоками мы используем логику:
            -если количество блоков кратно трем то…
            -если количество блоков не кратно трем и нечетное то…

            Соответственно, мы можем обратиться к любому количеству элементов, которое удовлетворяет условию an+b(т.е. фиксированная база + кратность)

            Пример с ~ более читаем, так как мы сразу видим, например:
            :nth-last-child(3n):first-child(если элемент, кратный трем, если считать с конца, является первым, то есть количество элементов кратно трем), ~ :nth-child(3n — 1) (обратиться к каждому третьему элементу начиная с второго)
              0
              codepen.io/anon/pen/ebfGI — вот так у меня поломалось
                0
                эээ, оно работает
                там 14 элементов, это не попадает под условие «кратно трем» и не попадает под условие «не кратно трем и нечетное».

                Если хотите — я сделал другой пример, с ним тоже можно поиграться.
                Три элемента в колонке, первая колонка состоит из 1, 2 или 3 элементов в зависимости от количества элементов в блоке

                codepen.io/Jabher/pen/atkwe
          • UFO just landed and posted this here
              +1
              nth-* плохо влияет на производительность, т.к. при его использовании отключаются некторое оптимизации движка CSS рендеренга
              • UFO just landed and posted this here
                  0
                  Нет, почему, кроссбраузерно, ie9+.

                  А что вы имеете ввиду под «вынуждена либо ждать полной загрузки предка, либо многократно пересчитываться по ее ходу»?
                  • UFO just landed and posted this here
                    0
                    Речь о том, что когда пачка элементов лежит внутри одной ноды, то правила применяются к ним скоупом.
                    Когда встречается nth-*, то для каждого элемента просчитываются правила индивидуально, и поведение с кешированием — применять ко всем детям сразу, отключается для всей страницы.
                    • UFO just landed and posted this here
                +1
                Статья не про CSS, а скорее про SASS. Вы бы хотя б результат компиляции в 2000 строк выложили.
                  0
                  Боюсь, фразу «осторожно! под катом много стилей» никто бы не воспринял всерьез
                    +1
                    Результат на codepen.io виден, если на заголовок окна с кодом нажать.

                  Only users with full accounts can post comments. Log in, please.