Плиточная раскладка на CSS Flexbox с помощью order и :nth-child()
Эта статья показывает то, как сделать плиточную раскладку на CSS Flexbox, имея под рукой только стандартные возможности CSS и ничего более. Она хороша тем, что показывает подводные камни работы с флексами и даёт понять суть их работы с другого ракурса.
Перевод статьи CSS masonry with flexbox, :nth-child(), and order
👉Мой Твиттер — там много из мира фронтенда, да и вообще поговорим🖖. Подписывайтесь, будет интересно: ) ✈️
С первого взгляда это кажется довольно простым, взять и создать плиточную раскладку на флексах. Всё, что нужно, это всего лишь выставить flex-flow
на column wrap
и вуаля, у вас то что нужно. Ну типа того. Проблема с таким подходом в том, что он создает сетку с на вид перетасованным и неясным порядком. Элементы будут отрендерены сверху вниз и люди, смотрящие на сетку слева направо будут читать боксы в примерно таком, довольно произвольном виде, к примеру 1, 3, 6, 2, 4, 7, 8, 5 и так далее и т.п
.
У флексов нет простого способа отрендерить элементы в виде колоночной раскладки, используя строчную расстановку элементов, но мы всё же можем сделать плиточную раскладку только на CSS — без JavaScript — используя свойства :nth-child()
и order
. По сути, это трюк с созданием строчной расстановки элементов, используя flex-direction: column
, учитывая то, что мы рендерим 3 колонки:
/* Рендерим элементы как колонки */
.container {
display: flex;
flex-flow: column wrap;
}
/* Меняем порядок элементов, делая как бы строчную расстановку */
.item:nth-child(3n+1) { order: 1; }
.item:nth-child(3n+2) { order: 2; }
.item:nth-child(3n) { order: 3; }
/* Новые колонки разделители */
.container::before,
.container::after {
content: "";
flex-basis: 100%;
width: 0;
order: 2;
}
Это создаст плиточную раскладку с элементами, отрендереными как колонки, но расставленными в порядке, как было бы в строчном порядке. Серые вертикальные линии это псевдоэлементы, которые делают переносы строк.
Давайте разберем проблему (ну или перейдите к её решению в конце статьи или к codepen).
Выбирайте каким будет косяк: смешанным порядком или странными промежутками
Flexbox по факту не создан для построения плиточных раскладок — если вы выставите фиксированную высотку на флекс-контейнере и flex-flow
на column wrap
, то у вас получится что-то типа такого:
Тут элементы отрендерены по колонкам сверху вниз, создавая произвольный порядок во время чтения слева направо. Конечно же, это ожидаемый итог и желаемый во многих случаях, но не тогда, когда мы пытаемся создать именно плиточную раскладку. Вы только представьте насколько это дезориентирует читателя, когда страница станет больше в высоту.
Если же мы сменим flex-direction
на row
, имея элементы с различными высотами, то мы получим правильный порядок, но уже со стрёмными и неожиданными разрывами по все нашей сетке:
Так, что, кажется невозможным воспользоваться преимуществами обоих подходов: получить элементы отрендеренные как колонки, но имеющие строчный порядок. Вы могли прибегнуть к flex-direction: column
и просто переставить элементы в HTML, достигнув правильного визуального порядка, но это может быть очень громоздко, к тому же это излишне сложно и создаст неразбериху в логическом порядке при переходах по клавише табуляции (Просто кликая по табу).
Меняем порядок элементов с помощью order и nth-child()
Свойство order
влияет на порядок элементов в CSS Flexbox или гриде, и мы можем смело использовать его для смены порядка элементов в нашей плиточной раскладке, которая вот вот уже будет сделана как мы хотим. Свойство order
довольно простое в использовании: если у вас два элемента и у одного стоит order: 1
, а у другого order: 2
, то элемент с order: 1
будет орендерен перед другим элементом, вне зависимости от их порядка в HTML исходнике.
В нашем случае, решение зависит от тонкостей спецификации order
: что случится, если два или более элементов будут с одинаковым order
? Какой из них будет первым? В этом случае, flexbox опирается на исходный код: тот элемент, который идет первым в исходном HTML коде, будет отрендерен перед другим элементом с таким же значением order
. Это даёт нам возможность легко перегруппировать элементы в сетке таким образом, что мы сможем сменить порядок расстановки элементов с колоночного на строчный, всё ещё рендеря эти строки, как колонки, используя nth-child()
.
Посмотрите на табличку ниже. Чтобы получить рациональный порядок с использованием flex-direction: row
, нам просто надо отрендерить элементы в стандартном порядке: 1, 2, 3, 4, 5, 6, и т.д
.
Но если нам надо получить тот же порядок с использованием flex-direction: column
, то тогда уже нам понадобится поменять сам порядок элементов, таким образом, чтобы он соответствовал порядку каждой колонки в таблице (а не каждой строки):
То есть первыми элементами в нашей флексбокс раскладке должны 1, 4, 7, 10
. Эти элементы заполнят первую колонку, далее 2, 5, 8, 11
для второй колонки и 3, 6, 9, 12
для третьей и последней колонки. Тут нам на помощь приходит селектор nth-child()
. Мы можем его использовать для того, чтобы выделить каждый третий элемент (3n)
, начиная с первого элемента (3n + 1)
и выставить всем этим элементам одинаковое значение order
:
.item:nth-child(3n+1) { order: 1; }
Этот селектор выставит order: 1
для элементов 1, 4, 7, 10
, то есть всем первым колонкам. Другими словами, мы используем комбинацию nth-child()
и order
, чтобы изменить порядок элементов на их изначальном положении. Чтобы создать 2 и 3 колонки, мы изменим порядок у других элементов:
.item:nth-child(3n+1) { order: 1; }
.item:nth-child(3n+2) { order: 2; }
.item:nth-child(3n) { order: 3; }
Тут мы делаем три группы элементов: 1, 4, 7, 10 (3n+1)
c order: 1
, далее 2, 5, 8, 11 (3n+2)
с order: 2
и 3, 6, 9, 12 (3)
. Всё вместе выдаёт нам такой порядок элементов 1, 4, 7, 10, 2, 5, 8, 11, 3, 6, 9, 12
.
Такой подход создаст иллюзию того, что элементы вернулись к своему изначальному порядку, как если бы вы рассматривали их в порядке слева направо. Если бы мы визуально оценивали эту сетку по строкам, то первая строка включала бы по первому элементу с каждой группы (1, 2, 3)
, вторая строка содержала бы каждый второй элемент из каждой группы (4, 5, 6)
и так далее и т.п. Используя эту технику мы можем создать плиточную раскладку с элементами, отрендеренными как колонки, но имеющими порядок как было бы в строчном варианте.
А как это влияет на переходы с использованием табов? Да никак. Свойство order
меняет только визуальное представление объектов, а не порядок переходов по клавише TAB, так что в этом плане всё будет работать, как и предполагается.
Предотвращаем слияние колонок
Если у вас довольно много элементов в раскладке, то этот подход 100% приведёт к тому, что что-то пойдет не так. Мы делаем расчёт на то, что каждая “группа”, которую мы создали, будет отрендерена ровно как одна колонка в контейнере, но в реальности у элементов могут быть разные высоты и колонки с лихвой могут сливаться друг с другом. Первая колонка может быть длиннее, чем другие две, для примера, что привело бы к тому, что третья колонка начиналась бы в конце второй:
Подсвеченный элемент (3)
должен быть отрендерен в начале третьей колонки или же алгоритм расстановки попросту полетит в тартарары, и если есть место для ещё одного элемента в конце второй колонки, то он само собой будет отрендерен там.
Мы можем пофиксить эту проблему, заставив колонки рестартиться в указанные моменты. Вообще, с флексбоксами нет простого способа для указания “этот элемент должен перейти на новую строку”, но мы можем достигнуть этого эффекта, добавив невидимые элементы, которые возьмут 100% высоты контейнера. Так как они требуют 100% высоты родителя, то они не поместятся в колонку вместе с любым другим элементом, создавая тем самым, переходы на новые строки.
Нам нужно вставить такие переходы в сетку и массив элементов, таким образом, чтобы у нас получилась такая последовательность: 1, 4, 7, 10,<переход>, 2, 5, 8, 11,<переход>, 3, 6, 9, 12
. Мы можем использовать псевдоэлементы на контейнере, чтобы добавить такие переходы и мы можем выставить order
на 2
обоим. Добавление псевдоэлемента :before, сделает его первым потомком контейнера, а добавление псевдоэлемента :after сделает его последним потомком того же контейнера, так что если мы выставим order: 2
каждому из них, то они станут первым и последним элементом “группы” с order: 2
(так как они находятся до и после других элементов): :before, 2, 5, 8, 11, :after
.
/* Новые колонки разделители */
.container::before,
.container::after {
content: "";
flex-basis: 100%;
width: 0;
order: 2;
}
Я подсветил псевдоэлементы ниже, чтобы показать их эффект. Обратите внимание, что элемент 3 мог бы попасть во вторую колонку, но он отображается как первый элемент в последней:
Решение
И в конце вам надо убедиться в том, что ваш флекс-контейнер имеет высоту выше, чем ваша самая высокая колонка (чтобы все колонки поместились). Теперь всё это совместите и так вы сможете сделать настоящую трехколоночную плиточную раскладку (вот codepen).
.container {
display: flex;
flex-flow: column wrap;
align-content: space-between;
/* Вашему контейнеру нужна фиксированная высота
* и он должен быть выше, чем самая высокая колонка. */
height: 600px;
}
.item {
width: 32%;
margin-bottom: 2%; /* Optional */
}
/* Перегруппировываем элементы в 3 ряда */
.item:nth-child(3n+1) { order: 1; }
.item:nth-child(3n+2) { order: 2; }
.item:nth-child(3n) { order: 3; }
/* Новые колонки разделители */
.container::before,
.container::after {
content: "";
flex-basis: 100%;
width: 0;
order: 2;
}
Ваш html должен выглядеть примерно так, где div
это каждый элемент в сетке:
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
...
</div>
Работаем с более, чем тремя колонками
Чтобы создать плиточную раскладку с более, чем тремя колонками, нам надо сделать несколько вещей: адаптировать наш алгоритм сортировки, обновить ширину элементов и вставить переходы на новые строки вручную (вместо использования псевдоэлементов). Для быстрого доступа к конечным результатам я собрал список codepen’ов показывающих плиточную раскладку на флексах для 3, 4, 5 и 6 колонок.
До этого мы были ограничены созданием только двух псевдоэлементов с :before
и :after
, теперь нам нужно прибегнуть к ручному добавлению переходов на новые строки внутри контейнера (вам требуется на один элемент-разделитель меньше, чем количество колонок в вашей раскладке). Вы можете просто вставить их в конец контейнера и они будут отсортированы по соответствующим колонкам:
<div class="container">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
...
<span class="item break"></span>
<span class="item break"></span>
<span class="item break"></span>
</div>
Мы вставляем разделители колонок как span, для того, чтобы отсортировать их в независимости от элементов контента. Нам нужен способ сбросить счет после того, как мы достигнем элемент разделитель или неравномерное число элементов сделает старт первого разделителя после 3 колонки, к примеру. Селектор :nth-of-type
выбирает элементы вне зависимости от их типа, так что мы можем разделить порядок элементов и разделители таким образом:
.item:nth-of-type(4n+1) { order: 1; }
.item:nth-of-type(4n+2) { order: 2; }
.item:nth-of-type(4n+3) { order: 3; }
.item:nth-of-type(4n) { order: 4; }
Разделители же, как и в предыдущем примере, возьмут всю высоту контейнера:
/* Переход к новой колонке */
.break {
flex-basis: 100%;
width: 0;
margin: 0;
}
В общем, это создаст плиточную колонку с 4-мя колонками (вот codepen):
Вот полное решение для CSS такой плиточной раскладки с 4-мя колонками:
.container {
display: flex;
flex-flow: column wrap;
align-content: space-between;
height: 600px;
}
.item {
width:24%;
margin-bottom: 2%; /* опционально */
}
.item:nth-of-type(4n+1) { order: 1; }
.item:nth-of-type(4n+2) { order: 2; }
.item:nth-of-type(4n+3) { order: 3; }
.item:nth-of-type(4n) { order: 4; }
.break {
flex-basis: 100%;
width: 0;
margin: 0;
}
Этот способ создания плиточной раскладки только средствами CSS, точно не является самым надежным, гибким или защищенным от внезапных косяков со стороны разработчика, в отличие от такого же исполнения на JavaScript (например Masonry), но если вы не хотите полагаться на сторонние библиотеки только для того, чтобы создать раскладку, то я надеюсь, что эти советы будут вам полезны.