03
Май
2019

CSS masonry с помощью flexbox, :nth-child() и order

CSS masonry с помощью flexbox, :nth-child() и order

От автора: на первый взгляд создать макет masonry с помощью flexbox довольно просто; все, что вам нужно сделать, это установить для flex-flow — column wrap, и вуаля, у вас есть макет masonry. Что-то вроде того. Проблема этого подхода заключается в том, что он создает сетку с визуально перетасованным и непонятным порядком. Элементы будут (без ведома пользователя) отображаться сверху вниз, и кто-то, анализирующий сетку слева направо, будет читать поля в произвольном порядке, например, 1, 3, 6, 2, 4, 7, 8, 5 и так далее, и тому подобное.

Во Flexbox нет простого способа вывести элементы с макетом column при использовании порядка row, но мы можем построить макет masonry CSS (JavaScript не требуется) — с помощью свойств :nth-child() и order. В сущности, вот прием для создания порядка row при использовании flex-direction: column, учитывая, что вы отображаете три столбца:

/* Отображение элементов в столбцах */
.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;
}

Это создаст макет masonry с элементами, представленными в виде столбцов, но упорядоченными в виде рядов (серые вертикальные линии представляют псевдо-элементы, которые создают разрывы строк):

CSS masonry с помощью flexbox, :nth-child() и order

Давайте разберем эту проблему (или вы можете посмотреть набор кодов).

Выберите меньшее из двух зол: перетасованный порядок или странные отступы

Flexbox на самом деле не рассчитан на создание masonry; если установить фиксированную высоту для flex-контейнера (так, чтобы элементы могли переноситься) и column wrap для flex-flow, вы получите что-то вроде этого:

CSS masonry с помощью flexbox, :nth-child() и order

Элементы отображаются в столбцах сверху вниз, создавая произвольный порядок при чтении слева направо. Это, конечно, ожидаемый результат, и он желателен во многих сценариях, но не тогда, когда мы пытаемся создать макет masonry, и он становится все более дезориентирующим по мере роста страницы.

Если мы вместо того, чтобы изменить значение flex-direction на row и получить элементы различной высоты, мы достигнем правильного порядка, то получим странные и неожиданные отступы:

CSS masonry с помощью flexbox, :nth-child() и order

Таким образом, кажется невозможным взять лучшее из обоих миров: элементы, представленные в виде столбцов, но упорядоченные в виде строк. Вы можете решить использовать flex-direction: column и просто перемещаться по элементам в HTML для достижения правильного визуального порядка, но это может быть громоздким, излишне сложным, и исказит порядок табуляции элементов.

Изменение порядка элементов с помощью order и nth-child()

Свойство order влияет на порядок элементов, содержащихся во flexbox или grid, и мы можем использовать его, чтобы переупорядочить элементы для нашего будущего макета masonry. Свойство order довольно просто в использовании: если у вас есть два элемента и один имеет order: 1, а другой order: 2, элемент с order: 1 отображается перед другим элементом, независимо от их порядка в исходном коде HTML.

Решение для CSS masonry зависит от деталей спецификации order: что произойдет, если два или более элементов имеют одинаковое значение order? Кто отображается первым? Flexbox использует порядок исходного кода: элемент, который указан первым в исходном коде, будет отображаться раньше других элементов с таким же значением order. Этот факт дает нам возможность легко перегруппировать элементы в сетке, так что мы можем изменить порядок со столбцов на строки, и при этом отображая строки, как столбцы, используя nth-child().

Посмотрите на таблицу ниже. Чтобы добиться нужного порядка с помощью flex-direction: row, нам просто нужно отобразить элементы в порядке по умолчанию: 1, 2, 3, 4, 5, 6 и т. д.

CSS masonry с помощью flexbox, :nth-child() и order

Если мы хотим добиться того же порядка при использовании, flex-direction: column, нам нужно изменить порядок элементов, чтобы он соответствовал порядку каждого столбца в таблице (а не каждой строки):

CSS masonry с помощью flexbox, :nth-child() и order

Т.е. первые элементы в нашем макете flexbox должны быть 1, 4, 7, 10. Эти элементы будут заполнять первый столбец, далее следуют 2, 5, 8, 11 — 2-й столбец, и 3, 6, 9, 12 — 3-й и последний столбец. Вот где в игру вступает селектор 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) с order: 1; 2, 5, 8, 11 (3n + 2) с order: 2; и 3, 6, 9, 12(3n) с order: 3. Все вместе дает следующий порядок — 1, 4, 7, 10, 2, 5, 8, 11, 3, 6, 9, 12.

Если мы проследим, чтобы каждая из этих групп отображалась как один столбец (и не более), это создаст иллюзию того, что элементы вернулись в свой первоначальный порядок при чтении слева направо. Если мы визуально анализируем сетку как строки, первый ряд будет содержать первый элемент из каждой группы (1, 2, 3), второй ряд будет содержать второй элемент из каждой группы ( 4, 5, 6) и так далее. С помощью этой техники мы можем создать макет masonry с элементами, отображаемыми в виде столбцов, но упорядоченными, как ряды.

CSS masonry с помощью flexbox, :nth-child() и order

Как влияет перетасовывание элементов вроде этого на порядок табуляции? К счастью, совсем не влияет. order изменяет только визуальное представление объектов, а не последовательность табуляции, поэтому табуляция в сетке будет работать так, как задумано.

Предотвращение слияния столбцов

Если в макете masonry много элементов, эта техника наверняка в какой-то момент сломается. Мы рассчитываем на то, что каждая созданная нами «группа» будет отображаться как один столбец, но в действительности элементы могут иметь разную высоту, и столбцы могут легко начать объединяться. Например, первый столбец может быть длиннее двух других, что может привести к началу третьего столбца в конце второго:

CSS masonry с помощью flexbox, :nth-child() и order

Выделенный здесь блок (3) должен отображаться в начале третьего столбца, иначе алгоритм сломается, но если в конце второго столбца есть место для другого элемента, он, естественно, будет отображаться там.

Мы можем исправить эту проблему, принудительно перезапуская столбцы в определенные моменты. Нет простого способа указать с помощью flexbox — «этот элемент должен переноситься в следующий ряд», но мы можем добиться этого эффекта, добавив невидимые элементы, которые занимают 100% высоты контейнера. Поскольку для их отображения требуется 100% высоты родительского элемента, они не поместятся в столбце вместе с любым другим элементом, поэтому они по существу будут вызывать перенося строк, создавая разорванные столбцы.

Нам нужно вставить эти элементы разрыва строки в сетку и массив элементов, чтобы создать такую последовательность элементов будет: 1, 4, 7, 10, <break>, 2, 5, 8, 11, <break>, 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 будет помещаться во 2-ом столбце, он отображается как первый элемент в последнем столбце:

CSS masonry с помощью flexbox, :nth-child() и order

Решение

В качестве последнего шага нам нужно обеспечить, чтобы flex-контейнер имел заданную высоту, которая делает его выше, чем ваш самый высокий столбец (чтобы поместились все столбцы). Вместе, это создаст CSS макет masonry с тремя столбцами (также доступно на codepen):

.container {
  display: flex;
  flex-flow: column wrap;
  align-content: space-between;
  /* Для контейнера нужна фиксированная высота, и он 
   * должен быть выше самого высокого элемента. */
  height: 600px; 
}

.item {
  width: 32%;
  margin-bottom: 2%; /* Опционально */
}

/* Реорганизуем элементы в 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>

Работа более чем с тремя столбцами

Чтобы создать макет masonry с более чем тремя столбцами, нам нужно сделать несколько вещей: адаптировать наш алгоритм сортировки, обновить ширину элементов и вручную вставить элементы разрыва строки (вместо использования псевдо-элементов). Я составил список кодов, демонстрирующих flexbox masonry с 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;
}

Это создаст макет с четырьмя столбцами (см. Codepen):

CSS masonry с помощью flexbox, :nth-child() и order

Вот полное решение для CSS masonry с четырьмя столбцами:

.container {
  display: flex;
  flex-flow: column wrap;
  align-content: space-between;
  /* Для контейнера нужна фиксированная высота, и он 
   * должен быть выше самого высокого элемента. */
  height: 600px; 
}

.item {
  width:24%;
  margin-bottom: 2%; /* Optional */
}

/* Реорганизуем элементы в 4 ряда */
.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;
}

Этот способ создания макета masonry (или мозаики), безусловно, не такой стабильный, гибкий и надежный, как JavaScript реализация (например, Masonry ), но если вы не хотите применять стороннюю библиотеку только для создания макет кладки masonry, я надеюсь, что эти приемы макетов могут вам пригодиться.

Для получения информации по другим распространенным шаблонам CSS flexbox, я составил список примеров flexbox, которые вы можете скопировать и вставить в свои проекты.

Автор: Tobias Bjerrome Ahlin

Источник: https://tobiasahlin.com

Редакция: Команда webformyself.

Share

Тебе может это понравится...