Только для CSS макет кладки

Мне нужно реализовать довольно заурядный макет кладки. Однако по ряду причин я не хочу использовать JavaScript для этого.

A grid of multiple columns of rectangles of varying height.

Параметры:

  • Все элементы имеют одинаковую ширину
  • Элементы имеют высоту, которая не может быть рассчитана на стороне сервера (изображение плюс различные объемы текста)
  • Я могу жить с фиксированным количеством столбцов, если мне нужно

Существует тривиальное решение, которое работает в современных браузерах, свойство column-count.

Проблема с этим решением состоит в том, что элементы упорядочены в столбцах:

Starting from the top leftmost box, they're numbered 1 through 4 straight down, the topmost box in the next column is 5, and so on.

Хотя мне нужно упорядочить элементы в строках, хотя бы примерно:

Starting from the top leftmost box, they're numbered 1 through 6 straight across, but because box 5 is the shortest the box underneath it is 7 as it has the appearance of being on a row higher than the next box on the far left.

Подходы, которые я пробовал, не работают:

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

Есть ли какое-то новое волшебство flexbox, которое делает это возможным?

Ответ 1

Flexbox

Динамическое расположение каменной кладки невозможно с flexbox, по крайней мере, не в чистом и эффективном виде.

Flexbox - это система одномерного макета. Это означает, что он может выравнивать элементы по горизонтальным или вертикальным линиям. Сгибаемый элемент ограничен его строкой или столбцом.

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

Вот почему flexbox имеет ограниченную емкость для построения сеток. Это также причина, по которой W3C разработал еще одну технологию CSS3, Grid Layout.


row wrap

В гибком контейнере с flex-flow: row wrap, элементы flex должны переноситься на новые строки.

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

Обратите внимание выше, как div # 3 переносится ниже div # 1, создавая новую строку. Это не может обернуться ниже div # 2.

В результате, когда элементы не самые высокие в ряду, остаются пустые места, создавая неприглядные промежутки.


column wrap

Если вы переключитесь на flex-flow: column wrap, сетчатый макет станет более доступным. Однако у контейнера в направлении столбца есть четыре потенциальных проблемы:

  1. Гибкие элементы текут вертикально, а не горизонтально (как вам нужно в этом случае).
  2. Контейнер расширяется горизонтально, а не вертикально (как макет Pinterest).
  3. Требуется, чтобы контейнер имел фиксированную высоту, чтобы элементы знали, куда их оборачивать.
  4. На момент написания статьи он имел недостаток во всех основных браузерах, где контейнер не расширяется для размещения дополнительных столбцов.

В результате контейнер в направлении столбца не является опцией в этом случае и во многих других случаях.


Сетка CSS с размерами элемента не определена

Расположение сетки было бы идеальным решением вашей проблемы, если бы различные высоты элементов контента могли быть предварительно определены. Все остальные требования находятся в пределах мощности Grid.

Ширина и высота элементов сетки должны быть известны, чтобы закрыть промежутки с окружающими элементами.

Таким образом, Grid, который является лучшим CSS, который может предложить для построения горизонтального потока кладки, в этом случае не дотягивает.

Фактически, пока не появится технология CSS с возможностью автоматического устранения пробелов, у CSS в целом нет решения. Что-то вроде этого, вероятно, потребует перефразирования документа, поэтому я не уверен, насколько он будет полезен или эффективен.

Вам понадобится сценарий.

Решения JavaScript, как правило, используют абсолютное позиционирование, которое удаляет элементы контента из потока документов, чтобы переставить их без пропусков. Вот два примера:


CSS Grid с определенными размерами элемента

Для макетов, в которых известны ширина и высота элементов содержимого, здесь используется горизонтальный макет кладки в чистом CSS:

grid-container {
  display: grid;                                                /* 1 */
  grid-auto-rows: 50px;                                         /* 2 */
  grid-gap: 10px;                                               /* 3 */
  grid-template-columns: repeat(auto-fill, minmax(30%, 1fr));   /* 4 */
}

[short] {
  grid-row: span 1;                                             /* 5 */
  background-color: green;
}

[tall] {
  grid-row: span 2;
  background-color: crimson;
}

[taller] {
  grid-row: span 3;
  background-color: blue;
}

[tallest] {
  grid-row: span 4;
  background-color: gray;
}

grid-item {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3em;
  font-weight: bold;
  color: white;
}
<grid-container>
  <grid-item short>01</grid-item>
  <grid-item short>02</grid-item>
  <grid-item tall>03</grid-item>
  <grid-item tall>04</grid-item>
  <grid-item short>05</grid-item>
  <grid-item taller>06</grid-item>
  <grid-item short>07</grid-item>
  <grid-item tallest>08</grid-item>
  <grid-item tall>09</grid-item>
  <grid-item short>10</grid-item>
  <grid-item tallest>etc.</grid-item>
  <grid-item tall></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
  <grid-item short></grid-item>
  <grid-item tallest></grid-item>
  <grid-item taller></grid-item>
  <grid-item short></grid-item>
  <grid-item tallest></grid-item>
  <grid-item tall></grid-item>
  <grid-item short></grid-item>
</grid-container>

Ответ 2

Это недавно обнаруженная техника с использованием flexbox: https://tobiasahlin.com/blog/masonry-with-css/.

Статья имеет смысл для меня, но я не пытался ее использовать, поэтому я не знаю, есть ли какие-то предостережения, кроме упомянутых в ответе Майкла.

Вот пример из статьи, использующий свойство order, в сочетании с :nth-child.

Фрагмент стека

.container {
  display: flex;
  flex-flow: column wrap;
  align-content: space-between;
  /* Your container needs a fixed height, and it 
   * needs to be taller than your tallest column. */
  height: 960px;
  
  /* Optional */
  background-color: #f7f7f7;
  border-radius: 3px;
  padding: 20px;
  width: 60%;
  margin: 40px auto;
  counter-reset: items;
}

.item {
  width: 24%;
  /* Optional */
  position: relative;
  margin-bottom: 2%;
  border-radius: 3px;
  background-color: #a1cbfa;
  border: 1px solid #4290e2;
  box-shadow: 0 2px 2px rgba(0,90,250,0.05),
    0 4px 4px rgba(0,90,250,0.05),
    0 8px 8px rgba(0,90,250,0.05),
    0 16px 16px rgba(0,90,250,0.05);
  color: #fff;
  padding: 15px;
  box-sizing: border-box;
}

 /* Just to print out numbers */
div.item::before {
  counter-increment: items;
  content: counter(items);
}

/* Re-order items into 3 rows */
.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; }

/* Force new columns */
.break {
  flex-basis: 100%;
  width: 0;
  border: 1px solid #ddd;
  margin: 0;
  content: "";
  padding: 0;
}

body { font-family: sans-serif; }
h3 { text-align: center; }
<div class="container">
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 190px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 120px"></div>
  <div class="item" style="height: 160px"></div>
  <div class="item" style="height: 180px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 150px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 190px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 120px"></div>
  <div class="item" style="height: 160px"></div>
  <div class="item" style="height: 180px"></div>
  <div class="item" style="height: 140px"></div>
  <div class="item" style="height: 150px"></div>
  <div class="item" style="height: 170px"></div>
  <div class="item" style="height: 170px"></div>
  
  <span class="item break"></span>
  <span class="item break"></span>
  <span class="item break"></span>
</div>

Ответ 3

Я столкнулся с похожим вопросом. Выровняйте элементы строки сетки, но не в строке, и не нашел ответа для своего случая. Вот альтернативное решение, которое полезно для текстовых блоков.

Вертикально-проточная сетка enter image description here

// Code was written by raziEiL. Feel free to use.
const LINE_HIEGHT = document.getElementById("fake-item").offsetHeight;
const ITEM_ARRAY = document.getElementsByClassName("item");
const BTN = document.getElementsByClassName("btn")[0];
const GRID = document.getElementsByClassName("grid")[0];

var isFixed = false;
BTN.style.background = "lightgreen";

function onClick(){
  isFixed = !isFixed;
  BTN.style.background = isFixed ? "red" : "lightgreen";
  alignGridRows();
}

function onResize() {
    alignGridRows();
}

function alignGridRows() {
    GRID.style.alignItems = "start";
    for (let i = 0; i < ITEM_ARRAY.length; i++) {
      ITEM_ARRAY[i].style.gridRow = isFixed ? "span " + Math.round(ITEM_ARRAY[i].offsetHeight / LINE_HIEGHT) : null;
    }
  GRID.style.alignItems = null;
}
body {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-evenly;
  height: 100vh;
  padding: 12px;
  margin: 0;
}
.info {
  background: yellow;
  width: 50%;
  max-width: 600px;
  padding: 0 6px;
}
.btn {
  font-size: 1.125em;
  padding: 10px;
}
/* Core code */
.grid {
    border: 2px solid gray;
    width: 100%;
    margin: 12px;
    display: grid;
    grid-gap: 6px;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.item {
  font-size: 1.2em;
  background: lightblue;
  border: 1px dotted gray;
}
/* Uses to detect line height of item class */
#fake-item {
    top: 0;
    left: 0;
    position: absolute;
    visibility: hidden;
    height: auto;
    width: auto;
    white-space: nowrap;
}
<body onresize="onResize()">
  <div class="info">
    <h1>Information</h1>
    <h2>Purpose</h2>
    <p>Automatically calculate text height and telling grid items how many rows they should span across.<br>Why? It removes vertical empty space from grid items.<p>
    <h2>How to use</h2>
    <p>Toggle the button to see difference. Start to resize window.<br><br>Code was written on pure css/js. Feel free to use!<br>Author: raziEiL</p>
  </div>
  <div class="grid">
    <div class="item">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
    <div class="item">Lorem ipsum dolor sit amet</div>
    <div class="item">Lorem ipsum dolor sit amet</div>
    <div class="item">Lorem ipsum dolor sit amet</div>
    <div class="item">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa.</div>
    <div class="item">Lorem ipsum dolor sit amet</div>
    <div class="item">Lorem ipsum dolor sit amet</div>
    <div class="item">Lorem ipsum dolor sit amet</div>
    <div class="item">Lorem ipsum dolor sit amet</div>
  </div>
  <div id="fake-item" class="item">Uses to detect line height of item class</div>
  <button class="btn" onclick="onClick()">Toggle items alignment</button>
</body>