Создание горизонтального и вертикального шагового компонента с помощью CSS flexbox
На днях у меня возник вопрос о том, как создать отзывчивый степперный компонент и как позаботиться о разделительных линиях. Демонстрация, которую я получил от разработчика, была немного сложной и содержала много ненужного CSS-материала. В результате у меня появилась идея написать об этом.
В этой статье я рассмотрю различные конструкции степпинг-компонента и найду лучший способ реализовать каждый из них в HTML и CSS. Вы готовы? Давайте углубимся.
Введение
Пошаговый компонент можно использовать для упрощения навигации по длинной веб-странице путем разделения связанных элементов и группировки их вместе.
Когда я начал изучать CSS, это было для меня сложной задачей, и я понимаю, почему некоторые новички могут чувствовать то же самое. В следующих нескольких разделах я буду рассматривать конкретные примеры и попытаюсь упростить и объяснить, как решать каждый из них.
Горизонтальный степпер: пример 1
В этом примере у нас есть горизонтальный степпер с линией между элементами. Вот ожидания от степпера:
- Отзывчивый
- Легко изменить размер
- Магические числа не допускаются
- Работа как со светлым, так и с темным режимом
<ol class="c-stepper">
<li class="c-stepper__item">
<h3 class="c-stepper__title">Step 1</h3>
<p class="c-stepper__desc">Some desc text</p>
</li>
<!-- Other steps -->
</ol>
В CSS нам нужно использовать flexbox
для горизонтального размещения элементов.
.c-stepper {
display: flex;
flex-wrap: wrap;
}
.c-stepper__item {
flex: 1;
display: flex;
flex-direction: column;
text-align: center;
}
.c-stepper__item:before {
--size: 3rem;
content: "";
position: relative;
z-index: 1;
display: block;
width: var(--size);
height: var(--size);
border-radius: 50%;
margin: 1rem auto 0;
}
При этом мы получим следующий результат.
Как видите, содержимое расположено по центру, и каждый элемент равен другим элементам того же уровня благодаря flex: 1
. Далее нам нужно изучить, как добавить линию между ними.
.c-stepper__item:not(:last-child):after {
content: "";
height: 2px;
background-color: #e0e0e0;
}
Вы можете задаться вопросом, как псевдоэлемент занимает всю ширину, если на самом деле у него нет явной width
? Ну, он растягивается, чтобы заполнить все горизонтальное пространство, потому что это гибкий элемент.
Во-первых, нам нужно переместить его наверх. Поскольку это гибкий элемент, мы можем получить преимущество от свойства order
.
.c-stepper__item:not(:last-child):after {
content: "";
height: 2px;
background-color: #e0e0e0;
order: -1;
}
Теперь мы хотим расположить его правильно, но без использования position: absolute
, так как это не нужно.
.c-stepper__item:not(:last-child):after {
content: "";
position: relative;
top: 1.5rem;
left: 50%;
height: 2px;
background-color: #e0e0e0;
order: -1;
}
Вот объяснение приведенного выше CSS:
position: relative
для управления линией, не выводя ее из документооборота.- Значение
top
равно половине высоты круга. left: 50%
, чтобы линия начиналась от центра круга до центра круга следующего элемента.- Это позволит нам раскрасить каждую строку в соответствии с прогрессом, если это необходимо.
Версия, в которой есть пробел до и после строки
Мы можем либо добавить обводку вокруг каждого круга с одинаковым фоном под элементами, либо мы можем немного поработать с лучшим решением, которое отлично работает как для темного, так и для светлого режимов.
Прежде чем погрузиться в решение, я хочу показать вам, что линии-разделители, которые у нас есть, на самом деле скрыты под каждым кругом. Я уменьшил opacity
для числовых кругов, чтобы вы могли их видеть.
Это связано с использованием left: 50%
для строки. Давайте рассмотрим, как придать ему смещение слева и справа.
Так как линия имеет left: 50%
, она будет начинаться с центра своего родителя.
Мы используем calc()
, чтобы добавить радиус круга, это заставит разделительную линию начинаться с конца круга.
Добавив нужный интервал (в данном случае 8 пикселей) в функцию calc()
, мы получим пробел слева от разделительной линии.
Наконец, нам нужно создать интервал с другой стороны. Ширина равна 100%, поэтому мы вычитаем ширину круга вместе со значениями интервала слева и справа.
Чтобы сделать его лучше, мы можем воспользоваться преимуществами переменных CSS, чтобы мы могли изменять размер без ручного редактирования значений.
.c-stepper {
--size: 3rem;
--spacing: 0.5rem;
}
.c-stepper__item:not(:last-child):after {
width: calc(100% - var(--size) - calc(var(--spacing) * 2));
left: calc(50% + calc(var(--size) / 2 + var(--spacing)));
}
Посмотрите видео ниже для демонстрации.
Наконец, реализация такого разделителя может заставить степпер работать в темном режиме без добавления границы к каждому кругу для имитации эффекта.
Демо
Вот живая демонстрация:
Горизонтальный степпер: пример 2
В этом примере разделительная строка идет сразу после заголовка шага. Разница здесь в том, что длина разделителя зависит от длины заголовка шага.
<ol class="c-stepper">
<li class="c-stepper__item">
<h3 class="c-stepper__title">Step 1</h3>
</li>
<!-- Other steps -->
</ol>
Как и в предыдущем примере, мы будем использовать flexbox
для размещения шагов по горизонтали. Обратите внимание, что мы хотим применить flex: 1
только к первому и второму шагу.
.c-stepper {
display: flex;
flex-wrap: wrap;
}
.c-stepper__item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.c-stepper__item:not(:last-child) {
flex: 1;
}
.c-stepper__item:before {
--size: 3rem;
content: "";
display: block;
flex: 0 0 var(--size);
height: var(--size);
border-radius: 50%;
}
Строка-разделитель будет добавлена как псевдоэлемент. Он будет иметь flex: 1
, чтобы он мог заполнить оставшееся пространство.
.c-stepper__item:not(:last-child):after {
content: "";
flex: 1;
height: 2px;
background-color: #e0e0e0;
margin-inline-end: 0.5rem;
}
Используя gap
в .c-stepper__item
и логическое свойство margin-inline-end
для строки-разделителя, мы можем гарантировать, что компонент будет работать как с документами LTR, так и с RTL.
Демо
Вот живая демонстрация:
Вертикальный степпер: пример 3
Это похоже на исходный пример, но направление вертикальное. Я буду использовать flexbox
для компоновки элементов.
<ol class="c-stepper">
<li class="c-stepper__item">
<div class="c-stepper__content">
<h3 class="c-stepper__title">Step 2</h3>
<p>Some desc text</p>
</div>
</li>
<!-- Other steps -->
</ol>
.c-stepper__item {
display: flex;
gap: 1rem;
}
.c-stepper__item:before {
--size: 3rem;
content: "";
position: relative;
z-index: 1;
flex: 0 0 var(--size);
height: var(--size);
border-radius: 50%;
background-color: lightgrey;
}
Теперь к интересной части, а именно к разделительной линии. Строка должна быть немного выше содержимого. Как мы можем добиться этого, избегая использования фиксированной высоты или полей?
Мы можем добавить большое padding-bottom
для этой цели.
.c-stepper__item {
position: relative;
display: flex;
gap: 1rem;
padding-bottom: 4rem;
}
.c-stepper__item:not(:last-child):after {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
transform: translateX(1.5rem);
width: 2px;
background-color: #e0e0e0;
}
Чтобы создать пробел между разделительной линией и числами, мы можем применить ту же формулу, что и в примере с горизонтальным степпером, или просто использовать свойства top
и bottom
.
.c-stepper {
--size: 3rem;
--spacing: 0.5rem;
}
.c-stepper__item:not(:last-child):after {
top: calc(var(--size) + var(--spacing));
transform: translateX(calc(var(--size) / 2));
bottom: var(--spacing);
}
При этом у нас может быть расстояние между линией и кругом.
Демо
Вот живая демонстрация:
Вертикальный степпер: пример 4
Это может быть не похоже на степпер, это скорее временная шкала. Независимо от названия, реализовать это можно с помощью flexbox
.
Во-первых, HTML-разметка выглядит так, как показано ниже. Обратите внимание, что элементы упорядочены в соответствии с их семантикой и важностью, а не в соответствии с макетом дизайна.
<ol class="c-timeline">
<li class="c-timeline__item">
<div class="c-timeline__content">
<h3 class="c-timeline__title">Paris</h3>
<p class="c-timeline__desc">On time</p>
</div>
<time class="c-timeline__time">10:03</time>
</li>
<!-- Other items -->
</ol>
Вот базовый CSS.
.c-timeline__item {
display: flex;
gap: 1.5rem;
}
.c-timeline__content {
order: 1;
/* Reorder the content as per the design. */
padding-bottom: 3rem;
}
При этом мы получим следующий результат. Хорошее начало!
Далее мы хотим решить, где разместить разделительную линию и маленький кружок. Я подумал, что .c-timeline__content
идеально подходит для этой работы. Мы можем использовать два псевдоэлемента как для разделителя, так и для окружности.
/* The separator line */
.c-timeline__item:not(:last-child) .c-timeline__content:before {
content: "";
position: absolute;
right: 100%;
top: 0;
height: 100%;
width: 2px;
background-color: #d3d3d3;
}
/* The circle */
.c-timeline__content:after {
content: "";
position: absolute;
left: -12px;
top: 0;
width: 20px;
height: 20px;
background-color: #fff;
z-index: 1;
border: 2px solid #d3d3d3;
border-radius: 50%;
}
Далее нам нужно ограничить ширину элемента времени. Кроме того, содержимое временной шкалы должно занимать оставшееся доступное пространство, поэтому мы будем использовать flex: 1
.
.c-timeline__time {
flex: 0 0 100px;
}
.c-timeline__content {
flex: 1;
}
Следующий шаг — выровнять содержимое c-timeline__time
по правому краю или до конца в логике CSS.
.c-timeline__time {
flex: 0 0 100px;
text-align: end;
}
Я хочу выделить важный момент, касающийся времени каждого элемента. В некоторых случаях мы не можем контролировать контент, автор может добавить очень длинный контент намеренно или по ошибке.
По умолчанию гибкие элементы не уменьшаются ниже минимального размера содержимого. Если элемент .c-timeline__time
имеет очень длинное содержимое, он будет выглядеть так, даже если у него flex: 0 0 100px
.
Чтобы исправить это, нам нужно заставить flexbox
уменьшиться ниже минимального размера содержимого, добавив к элементу min-width: 0
, а также overflow-wrap
, чтобы разбить длинные слова.
.c-timeline__time {
flex: 0 0 100px;
text-align: end;
min-width: 0;
overflow-wrap: break-word;
padding-bottom: 1rem;
}
Демо
Вот живая демонстрация:
Надеюсь, вам понравилась статья. Спасибо за прочтение!