Как можно сверстать подобный блок последовательных пунктов roadmap на HTML и CSS?
Допустим есть вот такой
. Как его можно сверстать адаптивно на чистом HTML и CSS? На флексбоксах такое реализуемо или подобное делается по другому?
Ответы (2 шт):
Основная проблема получить универсальное решение при помощи только HTML и CSS заключается в порядке следования элементов. Блоки следуют друг за другом в едином потоке, а при разрыве прыгают вниз. В нашем же случае, каждая четная образуемая строка должна иметь обратный порядок элементов. И нет никаких способов выделить такую строку и в рамках нее изменить порядок. А если мы обернем строки в группы, то мы лишимся адаптивности, т.к. структура элементов будет фиксирована.
Первый подход, который мы рассмотрим, это частное решение задачи, где мы вручную будем задавать порядок и стилизовать каждый элемент для всех @media. Для организации структуры будем использовать Grid.
Имеем семь блоков. При выводе в две колонки нам нужна такая последовательность:
1 2 4 3 5 6 8 7
Восьмой блок – пустышка, чтобы правильно расставить блоки в Grid.
Для четырех колонок:
1 2 3 4 8 7 6 5
У Flex/Grid есть свойство order. Задать порядок нужному элементу можно так:
.block:nth-child(1) { order: 1; }
.block:nth-child(2) { order: 2; }
…
Для трех контрольных точек @media получим следующее:
.chain {
display: grid;
gap: 10px;
}
.block {
height: 100px;
background: yellow;
font-size: 200%;
}
/* 2 rows */
@media (min-width: 500px) and (max-width: 749px) {
.chain { grid-template-columns: 1fr 1fr; }
.block:nth-child(1) { order: 1; }
.block:nth-child(2) { order: 2; }
.block:nth-child(3) { order: 4; }
.block:nth-child(4) { order: 3; }
.block:nth-child(5) { order: 5; }
.block:nth-child(6) { order: 6; }
.block:nth-child(7) { order: 8; }
.empty { order: 7; }
}
/* 4 rows */
@media (min-width: 750px) {
.chain { grid-template-columns: 1fr 1fr 1fr 1fr; }
.block:nth-child(1) { order: 1; }
.block:nth-child(2) { order: 2; }
.block:nth-child(3) { order: 3; }
.block:nth-child(4) { order: 4; }
.block:nth-child(5) { order: 8; }
.block:nth-child(6) { order: 7; }
.block:nth-child(7) { order: 6; }
.empty { order: 5; }
}
<div class="chain">
<div class="block">1</div>
<div class="block">2</div>
<div class="block">3</div>
<div class="block">4</div>
<div class="block">5</div>
<div class="block">6</div>
<div class="block">7</div>
<div class="empty"></div>
</div>
Стилизация бордюрами всего этого добра – то еще удовольствие.
Добавим стили под конкретную структуру (приведенную в вопросе):
:root {
--color: #3f48cc;
--line-size: 4px;
--line-style: solid;
--border: var(--line-size) var(--line-style) var(--color);
--dot-offset: 10px;
font-family: arial;
}
.chain {
display: grid;
color: var(--color);
padding-top: 40px;
}
.block { padding: 50px 0; }
.empty { order: 999; }
.block.first { margin-left: 10px; }
.num {
width: 20px;
margin-top: -87px;
margin-left: -2px;
padding-bottom: 6px;
border-bottom: 20px solid;
}
.block h2 {
font-size: 120%;
font-weight: normal;
margin-top: 0.3em;
}
.block p { font-size: 80%; }
/* 2 rows */
@media (min-width: 500px) and (max-width: 749px) {
.chain { grid-template-columns: 1fr 1fr; }
.block:nth-child(1) { order: 1; }
.block:nth-child(2) { order: 2; }
.block:nth-child(3) { order: 4; }
.block:nth-child(4) { order: 3; }
.block:nth-child(5) { order: 5; }
.block:nth-child(6) { order: 6; }
.block:nth-child(7) { order: 8; }
.empty { order: 7; }
.block:nth-child(4n):nth-child(4n+1) {
background: red;
}
.block:nth-child(4n+2) { border-right: var(--border); }
.block:nth-child(4n):not(:last-child) {
border-left: var(--border);
padding-left: calc(var(--dot-offset) - var(--line-size));
}
.block:nth-child(4n):not(:last-child) + .block {
padding-left: var(--dot-offset);
}
.block:not(:last-child) { border-top: var(--border); }
}
/* 4 rows */
@media (min-width: 750px) {
.chain { grid-template-columns: 1fr 1fr 1fr 1fr; }
.block:nth-child(1) { order: 1; }
.block:nth-child(2) { order: 2; }
.block:nth-child(3) { order: 3; }
.block:nth-child(4) { order: 4; }
.block:nth-child(5) { order: 8; }
.block:nth-child(6) { order: 7; }
.block:nth-child(7) { order: 6; }
.empty { order: 5; }
.block:nth-child(8n+4) { border-right: var(--border); }
.block:nth-child(-n+7) { border-top: var(--border); }
}
/* 1 row */
@media (max-width: 499px)
{
.chain { padding: 0; }
.block:nth-child(-n+6) { border-left: var(--border); }
.block:nth-child(7) {
margin-left: calc(var(--line-size) + var(--dot-offset));
}
.block {
padding: 0 0 20px 10px;
margin-left: 10px;
}
.num {
margin: 0px 0 0 -22px;
padding: 0 0 0 5px;
border-bottom: none;
border-left: 20px solid;
}
}
<div class="chain">
<div class="block first">
<div class="num">1.</div>
<h2>Регистрация</h2>
</div>
<div class="block">
<div class="num">2.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">3.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">4.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">5.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">6.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">7.</div>
<h2>Сказке конец</h2>
<p>Описание</p>
</div>
<div class="empty"></div>
</div>
Плюсы: негромоздкий CSS, несложный скрипт JS делает решение универсальным.
Минусы: негибкое решение, для каждого набора и макета нужно колдовать CSS.
Второй подход. Каждую пару элементов пакуем в Grid. Каждую пару таких гридов пакуем еще в один Grid. Полученные пары добавляем в базовый Grid-контейнер, сколько нужно. Структура:
<chain>
<pair1>
<!-- только два узла внутри -->
<pair2>
<!-- только два элемента внутри -->
<block/>
<block/>
</pair2>
<pair2>
<block/>
<block/>
</pair2>
</pair1>
...
</chain>
Если pair1 и pair2 - одноколоночные гриды, то все элементы выведутся списком.
Если pair2 - двухколоночный грид, то получим макет с двумя колонками, где в каждом нечетном pair2 нужно поменять местами элементы.
Если pair1 и pair2 - двухколоночные гриды, то получим макет с четырьмя колонками, где в нечетных pair1 (и во вложенных pair2) меняем местами элементы. И т.д.
Таким образом, мы можем организовывать эту структуру в макеты, с кратным степени 2 числом колонок (1, 2, 4, 8, …):
.chain, .pair1, .pair2 {
display: grid;
gap: 10px;
}
.block {
height: 100px;
background: yellow;
font-size: 200%;
}
@media (min-width: 500px) and (max-width: 749px) {
.pair2 { grid-template-columns: 1fr 1fr; }
.pair2:nth-child(even) > :first-child { order: 1; }
}
@media (min-width: 750px) {
.pair1, .pair2 { grid-template-columns: 1fr 1fr; }
.pair1:nth-child(even) :nth-child(1) { order: 1; }
}
<div class="chain">
<div class="pair1">
<div class="pair2">
<div class="block">1</div>
<div class="block">2</div>
</div>
<div class="pair2">
<div class="block">3</div>
<div class="block">4</div>
</div>
</div>
<div class="pair1">
<div class="pair2">
<div class="block">5</div>
<div class="block">6</div>
</div>
<div class="pair2">
<div class="block">7</div>
<div></div>
</div>
</div>
</div>
Достаточно лаконичный CSS, где нам не требуется вручную задавать порядок каждого элемента, потому что есть селекторы
even/odd.
А вот стилизация убивает всю лаконичность:
:root {
--color: #3f48cc;
--line-size: 4px;
--line-style: solid;
--border: var(--line-size) var(--line-style) var(--color);
--dot-offset: 10px;
font-family: arial;
}
.chain, .pair1, .pair2 { display: grid; position: relative; }
.chain { color: var(--color); padding-top: 40px; }
.chain {
overflow: hidden;
}
.block {
padding: 50px 0;
width: 100%;
margin-left: var(--dot-offset);
}
.block.first { margin-left: 10px; }
.num {
width: 20px;
margin-top: -87px;
margin-left: -2px;
padding-bottom: 6px;
border-bottom: 20px solid;
}
.block h2 {
font-size: 120%;
font-weight: normal;
margin-top: 0.3em;
}
.block p { font-size: 80%; }
/*******/
@media (min-width: 500px) and (max-width: 749px) {
.pair2 { grid-template-columns: 1fr 1fr; }
.pair2:nth-child(even) > :first-child { order: 1; }
.pair1:not(:last-child) > :last-child:after {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: calc(-1 * var(--line-size));
width: var(--dot-offset);
border: var(--border);
border-right: none;
}
.pair2:not(:last-child) { border-right: var(--border); }
.pair2:not(:first-child) { margin-right: var(--line-size); }
.pair1:last-child >
.pair2:last-child:nth-child(odd) >
.block.last
{
border: none;
margin-top: var(--line-size);
}
.block { border-top: var(--border); }
}
@media (min-width: 750px) {
.pair1, .pair2 { grid-template-columns: 1fr 1fr; }
.pair1:nth-child(even) :nth-child(1) { order: 1; }
.pair1:nth-child(even):not(:last-child):after {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: calc(-1 * var(--line-size));
width: var(--dot-offset);
border: var(--border);
border-right: none;
}
.pair1:nth-child(odd ):not(:last-child) { border-right: var(--border); }
.pair1:nth-child(even):not(:last-child) { margin-right: var(--line-size); }
.pair1:last-child:nth-child(odd) .block.last
{
border: none;
margin-top: var(--line-size);
}
.block { border-top: var(--border); }
}
@media (max-width: 499px)
{
.chain { padding: 0; }
.block {
border-left: var(--border);
padding: 0 0 20px 10px;
}
.num {
margin: 0px 0 0 -22px;
padding: 0 0 0 5px;
border-bottom: none;
border-left: 20px solid;
}
.block.last
{
border: none;
margin-left: calc(var(--line-size) + var(--dot-offset));
}
}
<div class="chain">
<div class="pair1">
<div class="pair2">
<div class="block first">
<div class="num">1.</div>
<h2>Регистрация</h2>
</div>
<div class="block">
<div class="num">2.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
</div>
<div class="pair2">
<div class="block">
<div class="num">3.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">4.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
</div>
</div>
<div class="pair1">
<div class="pair2">
<div class="block">
<div class="num">5.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">6.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
</div>
<div class="pair2">
<div class="block">
<div class="num">7.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">8.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
</div>
</div>
<div class="pair1">
<div class="pair2">
<div class="block">
<div class="num">9.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="block">
<div class="num">10.</div>
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
</div>
<div class="pair2">
<div class="block last">
<div class="num">11.</div>
<h2>Приехали</h2>
<p>Описание</p>
</div>
<div></div>
</div>
</div>
</div>
Первый
.block должен быть .first, последний – .last. Если в паре только один элемент, то добавляем пустой div.
Плюсы: гибкое решение, можно добавить сколько угодно элементов, не трогая CSS.
Минусы: число колонок макета кратно степени 2. Более сложная структура узлов.
Итог изысканий: я бы использовал первый подход + скрипт JS с подпиской на MediaQueryListener.
UPD. Добавил еще одно решение отдельным ответом.
Ранее я опубликовал ответ с двумя подходами. Третий подход решил опубликовать отдельно.
Предыдущие варианты имели свои минусы, и я нашел более оптимальный. Наша задача – построить дорожную карту (roadmap) связанных элементов, организованных змейкой, с любым количеством элементов, без необходимости вручную прописывать CSS-правила для задания порядка каждого элемента. При этом, структура должна быть адаптивной, с любой конфигурацией макета (кол-во колонок) контрольных точек @media, и без JS.
Все звенья дорожной карты зададим простым линейным списком элементов, при этом каждому укажем CSS-переменную с номером:
<div class="link" style="--num: 1"></div>
<div class="link" style="--num: 2"></div>
<div class="link" style="--num: 3"></div>
...
Предположим, в нашем макете 4 колонки. Список элементов по номерам:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 …
Выделенные элементы – это четные строки змейки, элементы которой нужно менять местами. Эти группы элементов можно получить при помощи комбинации псевдоклассов :nth-child(An+B).
А вот изменение порядка следования элементов в группе и есть основная арифметическая хитрость подхода. Если для группы 5 6 7 8 сложить первый и последний элемент, получим 13. Теперь последовательно вычтем номера элементов из 13, и получим 8 7 6 5. Однако, внутри CSS-правила нам недоступен номер первого и последнего элемента в группе (строке), а только текущий --num. Но если немного поиграть с цифрами, зная число колонок --cols, можно получить такую формулу:
--base = --cols * (1 + 2 * F) + 1, где F = floor((--num - 1) / --cols)
Рассчитав для каждого элемента значение --base, получим:
--num 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 … --base 5 5 5 5 13 13 13 13 21 21 21 21 29 29 29 29 …
Нам остается лишь рассчитать свойство order для выборки элементов из четных строк:
order = --base - --num
К сожалению, математическая функция floor() в CSS только планируется, но можно воспользоваться @property, чтобы задать целочисленный тип параметра, и отнимать от значения 0.5, что и даст нам floor().
Пример с четырьмя контрольными точками адаптивности (1, 2, 3 и 4 колонки, проверять в Chrome):
@property --floor {
syntax: '<integer>';
initial-value: 0;
inherits: false
}
.roadmap {
--cols: 1;
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
gap: 10px;
}
.link {
--floor: calc(((var(--num) - 1) / var(--cols) - .5));
--base: calc(var(--cols) * (1 + 2 * var(--floor)) + 1);
order: var(--num);
height: 100px;
background: yellow;
font-size: 200%;
}
@media (min-width: 500px) and (max-width: 749px) {
.roadmap { --cols: 2; }
.link:nth-child(4n+3),
.link:nth-child(4n+4) {
background: orange;
order: calc(var(--base) - var(--num));
}
}
@media (min-width: 750px) and (max-width: 999px) {
.roadmap { --cols: 3; }
.link:nth-child(6n+4),
.link:nth-child(6n+5),
.link:nth-child(6n+6) {
background: orange;
order: calc(var(--base) - var(--num));
}
}
@media (min-width: 1000px) {
.roadmap { --cols: 4; }
.link:nth-child(8n+5),
.link:nth-child(8n+6),
.link:nth-child(8n+7),
.link:nth-child(8n+8) {
background: orange;
order: calc(var(--base) - var(--num));
}
}
<div class="roadmap">
<div class="link" style="--num: 1">1</div>
<div class="link" style="--num: 2">2</div>
<div class="link" style="--num: 3">3</div>
<div class="link" style="--num: 4">4</div>
<div class="link" style="--num: 5">5</div>
<div class="link" style="--num: 6">6</div>
<div class="link" style="--num: 7">7</div>
<div class="link" style="--num: 8">8</div>
<div class="link" style="--num: 9">9</div>
<div class="link" style="--num: 10">10</div>
<div class="link" style="--num: 11">11</div>
<div class="link" style="--num: 12">12</div>
</div>
Обратите внимание, какой лаконичный и ясный CSS. Совсем несложно масштабировать для любого числа колонок.
Теперь о грустном. Оказалось, поддержка @property оставляет желать лучшего, Safari и Firefox не проглотят, а это существенно. Поэтому оставим этот вариант на будущее.
Еще одна возможность рассчитать базовое число – через номер колонки элемента. Благо, назначить его элементам несложно через :nth-child(An+B).
Новая формула для расчета:
order = --num + --cols – 2 * --col - 1
Получится немного больше CSS:
.roadmap {
--cols: 1;
display: grid;
gap: 10px;
grid-template-columns: repeat(var(--cols), 1fr);
}
.link {
--col: 0;
order: var(--num);
height: 100px;
background: yellow;
font-size: 200%;
}
@media (min-width: 500px) and (max-width: 749px) {
.roadmap { --cols: 2; }
.link:nth-child(2n+1) { --col: 0; }
.link:nth-child(2n+2) { --col: 1; }
.link:nth-child(4n+3),
.link:nth-child(4n+4) {
order: calc(var(--num) + var(--cols) - 2 * var(--col) - 1);
background: orange;
}
}
@media (min-width: 750px) and (max-width: 999px) {
.roadmap { --cols: 3; }
.link:nth-child(3n+1) { --col: 0; }
.link:nth-child(3n+2) { --col: 1; }
.link:nth-child(3n+3) { --col: 2; }
.link:nth-child(6n+4),
.link:nth-child(6n+5),
.link:nth-child(6n+6) {
order: calc(var(--num) + var(--cols) - 2 * var(--col) - 1);
background: orange;
}
}
@media (min-width: 1000px) {
.roadmap { --cols: 4; }
.link:nth-child(4n+1) { --col: 0; }
.link:nth-child(4n+2) { --col: 1; }
.link:nth-child(4n+3) { --col: 2; }
.link:nth-child(4n+4) { --col: 3; }
.link:nth-child(8n+5),
.link:nth-child(8n+6),
.link:nth-child(8n+7),
.link:nth-child(8n+8) {
order: calc(var(--num) + var(--cols) - 2 * var(--col) - 1);
background: orange;
}
}
<div class="roadmap">
<div class="link" style="--num: 1">1</div>
<div class="link" style="--num: 2">2</div>
<div class="link" style="--num: 3">3</div>
<div class="link" style="--num: 4">4</div>
<div class="link" style="--num: 5">5</div>
<div class="link" style="--num: 6">6</div>
<div class="link" style="--num: 7">7</div>
<div class="link" style="--num: 8">8</div>
<div class="link" style="--num: 9">9</div>
<div class="link" style="--num: 10">10</div>
<div class="link" style="--num: 11">11</div>
<div class="link" style="--num: 12">12</div>
</div>
Итоговый стилизованный вариант:
:root {
--color: #3f48cc;
--line-size: 4px;
--line-style: solid;
--border: var(--line-size) var(--line-style) var(--color);
--left-offset: 10px;
font-family: arial;
}
.roadmap {
--cols: 1;
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
padding-left: var(--left-offset);
margin-top: 40px;
counter-reset: num 0;
color: var(--color);
}
.link {
--row-even: 0;
--col: 0;
padding-bottom: 40px;
order: var(--num);
border-top: var(--border);
counter-increment: num;
position: relative;
}
@media (min-width: 500px) and (max-width: 749px) {
.roadmap { --cols: 2; }
.link:nth-child(2n+1) { --col: 0; }
.link:nth-child(2n+2) { --col: 1; }
.link:nth-child(4n+3),
.link:nth-child(4n+4) {
order: calc(var(--num) + var(--cols) - 2 * var(--col) - 1);
--row-even: 1;
}
.link:nth-child(4n+4):not(.empty, .last):after { content: ''; }
.link:nth-child(4n+2):not(.empty, .last) { border-right: var(--border); }
}
@media (min-width: 750px) and (max-width: 999px) {
.roadmap { --cols: 3; }
.link:nth-child(3n+1) { --col: 0; }
.link:nth-child(3n+2) { --col: 1; }
.link:nth-child(3n+3) { --col: 2; }
.link:nth-child(6n+4),
.link:nth-child(6n+5),
.link:nth-child(6n+6) {
order: calc(var(--num) + var(--cols) - 2 * var(--col) - 1);
--row-even: 1;
}
.link:nth-child(6n+6):not(.empty, .last):after { content: ''; }
.link:nth-child(6n+3):not(.empty, .last) { border-right: var(--border); }
}
@media (min-width: 1000px) {
.roadmap { --cols: 4; }
.link:nth-child(4n+1) { --col: 0; }
.link:nth-child(4n+2) { --col: 1; }
.link:nth-child(4n+3) { --col: 2; }
.link:nth-child(4n+4) { --col: 3; }
.link:nth-child(8n+5),
.link:nth-child(8n+6),
.link:nth-child(8n+7),
.link:nth-child(8n+8) {
order: calc(var(--num) + var(--cols) - 2 * var(--col) - 1);
--row-even: 1;
}
.link:nth-child(8n+8):not(.empty, .last):after { content: ''; }
.link:nth-child(8n+4):not(.empty, .last) { border-right: var(--border); }
}
.link:not(.empty):before {
content: counter(num) '.';
position: absolute;
box-sizing: border-box;
height: 44px;
top: calc(-34px - var(--line-size) / 2);
left: 0;
width: 20px;
border-bottom: 20px solid var(--color);
}
.link:not(.last):after {
position: absolute;
width: 20px;
border: var(--border);
border-right: none;
left: calc(-1 * var(--left-offset));
top: calc(-1 * var(--line-size));
height: 100%;
}
.link.last {
border: none;
margin-top: calc(var(--line-size) * (1 - var(--row-even)));
border-top: calc(var(--line-size) * var(--row-even)) var(--line-style) var(--color);
}
.empty {
border: none;
padding: 0;
margin: 0;
height: 0;
}
.link h2 {
font-size: 120%;
font-weight: normal;
margin-bottom: 0.5em;
}
.link p {
font-size: 80%;
margin-top: 0em;
}
@media (max-width: 499px) {
.roadmap { margin-left: 20px; }
.link {
border: none;
border-left: var(--border);
padding-left: 30px;
}
.link h2 {
margin-top: -0.5em;
}
.link:not(.empty):before {
content: counter(num);
text-align: right;
top: -10px;
left: -49px;
padding-right: calc(var(--line-size) + 1px);
width: 65px;
border-bottom: none;
height: auto;
border-right: calc(var(--line-size) + 16px) solid var(--color);
}
.link.last { margin-left: var(--line-size); }
}
<div class="roadmap">
<div class="link" style="--num: 1">
<h2>Регистрация</h2>
</div>
<div class="link" style="--num: 2">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link" style="--num: 3">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link" style="--num: 4">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link" style="--num: 5">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link" style="--num: 6">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link" style="--num: 7">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link" style="--num: 8">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link" style="--num: 9">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link" style="--num: 10">
<h2>Следующий этап</h2>
<p>Описание</p>
</div>
<div class="link last" style="--num: 11">
<h2>Доигрались</h2>
</div>
<div class="link empty" style="--num: 12"></div>
<div class="link empty" style="--num: 13"></div>
<div class="link empty" style="--num: 14"></div>
</div>
Важно. К последнему звену в цепи нужно добавить класс .last, а после, необходимо добавить элементы с классом .empty, в количестве на единицу меньшем, чем максимальное число колонок в макетах. Они не видны, но важны для правильной организации элементов в последней строке.
Надеюсь кому-то пригодится.