Статьи

Создание мастера шагов с помощью BEM и Sass

Мастер шагов — это компонент пользовательского интерфейса, который помогает посетителю сайта узнать, что происходит во время действия с несколькими шагами. Например, онлайн-покупка. Он обычно используется в платежных формах, описывающих различные шаги для выполнения желаемого действия, а также текущий шаг.

Я думаю, что это хороший компонент пользовательского интерфейса, поскольку он не только помогает определить, сколько времени может занять действие, но также предоставляет некоторый контекст: что я уже сделал? Что осталось сделать? Могу ли я вернуться? Могу ли я пропустить этот шаг?

Эта статья не о плюсах и минусах такой схемы. Нет. Сегодня я хотел показать, как создать такой компонент с помощью CSS, чтобы быть в курсе многих вещей. В том числе:

  • Динамическая нумерация шагов;
  • Мобильный вид;
  • Визуально определите текущий шаг, выполненные шаги и оставшиеся шаги;
  • Использовать семантическую разметку;
  • Откат для старых браузеров;
  • Создавайте легко поддерживаемые стили.

Step Wizard

Разметка

Я думаю, что разметка для пошагового мастера должна быть упорядоченным списком. Поскольку шаги продвигаются один за другим, порядок имеет значение. Следовательно, упорядоченный список ( <ol> ). Каждый элемент должен быть элементом списка ( <li> ), содержащим ссылку ( <a> ) или анонимный элемент ( <span> ), если ссылки нет. Вот и все.

 <ol> <li> <a href="#">Cart</a> </li> <li> <a href="#">Authentication</a> </li> <li> <a href="#">Delivery</a> </li> <li> <span>Summary</span> </li> <li> <span>Payment</span> </li> </ol> 

Step Wizard 2

Что касается именования, я пошел с чем-то похожим на БЭМ (модификатор блочного элемента), так как это идеально подходит для такой методологии. Если вы не знакомы с БЭМ, я предлагаю вам прочитать введение этой статьи (кто еще) Гарри Робертс.

Используя БЭМ, мы получили бы атрибуты класса:

  • steps как блок;
  • steps__item для отдельных шагов;
    • steps__item--done для завершенного шага;
    • steps__item--current для текущего шага;
    • steps__item--first для первого шага (не обязательно с :first-of-type );
    • steps__item--last для последнего шага (не обязательно с :last-of-type );
  • steps__link для ссылок или промежутков.

Также мы добавим disabled атрибут к элементам, идущим после текущего шага.

Давайте добавим эти атрибуты класса к нашей предыдущей разметке, учитывая, что третий шаг является текущим (для демонстрационной цели):

 <ol class="steps"> <li class="steps__item steps__item--done steps__item--first"> <a href="#" class="steps__link">Cart</a> </li> <li class="steps__item steps__item--done"> <a href="#" class="steps__link">Authentication</a> </li> <li class="steps__item steps__item--active"> <a href="#" class="steps__link">Delivery</a> </li> <li class="steps__item" disabled="disabled"> <span class="steps__link">Summary</span> </li> <li class="steps__item steps__item--last" disabled="disabled"> <span class="steps__link">Payment</span> </li> </ol> 

Хорошо, это похоже на очень хорошее начало!

конфигурация

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

 /** * Text color * 1. Done and active steps * 2. Disabled steps */ $step-text-color: #333; /* 1 */ $step-text-color-disabled: #999; /* 2 */ /** * Background color * 1. Everything but active step * 2. Active step */ $step-background-color: #EFEFEF; /* 1 */ $step-background-color-active: #FFF; /* 2 */ /** * Number background color * 1. Disabled steps * 2. Done and active steps */ $step-counter-color: #BCBCBC; /* 1 */ $step-counter-color-active: #22A4BC; /* 2 */ /** * Border that's being used pretty much everywhere */ $step-border: 1px solid #CCC; /** * Common spacing value */ $step-baseline: .5em; /** * Breakpoint triggering mobile view */ $step-breakpoint: 767px; 

Теперь, когда мы установили все наши переменные, мы готовы двигаться вперед.

Чистая модель коробки

Во-первых, давайте использовать приличную коробочную модель.

 html { box-sizing: border-box; } *, :before, :after { box-sizing: inherit; } 

РАЗМЕР КОРОБКИ: ГРАНИЦЫ ВСЕ ВЕЩИ!

Контейнер

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

  • Скрыть нумерацию по умолчанию;
  • Инициализировать счетчик CSS на .steps ;
  • .steps__item счетчик на .steps__item ;
  • Отобразите счетчик с псевдоэлементом :before pseudo-element from .steps__item`.

Примечание: если вы не знакомы со счетчиками CSS, я предлагаю вам прочитать эту статью от меня на Codrops .

 /** * 1. Prevents user-selection from doing ugly things on screen * without causing any accessibility issue since there is no point * in selecting the step-wizard. * 2. Hides default numbering. * 3. Initializes a CSS counter called `steps` on the step-wizard. * 4. Clears inner floats. * 5. Resets initial padding. * 6. Applies vertical rhythm. */ .steps { user-select: none; /* 1 */ list-style: none; /* 2 */ counter-reset: steps; /* 3 */ overflow: hidden; /* 4 */ padding: 0; /* 5 */ margin: 0 0 ($step-baseline * 2) 0; /* 6 */ } 

шаг мастер-3

Предметы

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

 /** * Step item * 1. Putting them all on the same line * 2. Context positioning for pseudo-elements * 3. Incrementing `steps` counter at each new item * 4. Top and border bottoms only * 5. Making sure content doesn't wrap */ .steps__item { float: left; /* 1 */ position: relative; /* 2 */ counter-increment: steps; /* 3 */ border-top: $step-border; /* 4 */ border-bottom: $step-border; /* 4 */ white-space: nowrap; /* 5 */ background: $step-background-color; /** * Changing the cursor on disabled items * to something a bit more explicit. */ &[disabled] { cursor: not-allowed; } } 

шаг мастер-4

Определение размеров предметов

Вы, возможно, заметили, что мы еще не оценили шаги. Помните, как мы хотим разрешить любое количество предметов (по крайней мере, в приличном диапазоне)? Это означает, что нам нужно знать количество шагов для их определения. Ой, это грубо. Как мы собираемся это сделать?

Ну, на данный момент есть довольно много решений:

  • Используйте JavaScript для определения размера элементов;
  • Передайте количество шагов от бэкэнда / шаблона в атрибуте данных (например, data-steps-count );
  • Используйте немного CSS-волшебства.

Первые два решения имеют очень хорошую поддержку браузера, но я считаю плохой идеей использовать JavaScript для этой цели. Когда я впервые построил этот пошаговый мастер, я обычно передавал количество шагов в атрибуте данных, а затем использовал его следующим образом:

 // Loop from 1 through 10 because it is rather unlikely // to have a step-wizard hosting over 10 items. // In most cases, looping through 1 to 5 is more than enough. @for $i from 1 through 10 { .steps[data-steps-count="#{$i}"] .steps__item { width: (100% / $i); } } 

Это создаст CSS, подобный этому, с .steps__item соответствии со значением, переданным в атрибуте data:

 .steps[data-steps-count="1"] .steps__item { width: 100%; } .steps[data-steps-count="2"] .steps__item { width: 50%; } .steps[data-steps-count="3"] .steps__item { width: 33.33333%; } .steps[data-steps-count="4"] .steps__item { width: 25%; } .steps[data-steps-count="5"] .steps__item { width: 20%; } .steps[data-steps-count="6"] .steps__item { width: 16.66667%; } .steps[data-steps-count="7"] .steps__item { width: 14.28571%; } .steps[data-steps-count="8"] .steps__item { width: 12.5%; } .steps[data-steps-count="9"] .steps__item { width: 11.11111%; } .steps[data-steps-count="10"] .steps__item { width: 10%; } 

Так что это безопасный способ, который работает обратно в Internet Explorer 7 (а не 6, который не поддерживает селекторы атрибутов). Я пошел по пути сумасшествия CSS, вдохновленному техникой Леа Веру для стилизации элементов на основе количества братьев и сестер .

Основная идея состоит в том, чтобы использовать комбинацию из :first-child :nth-last-child и общего оператора братьев и сестер ( ~ ) для определения размера всех элементов из контейнера при наличии N элементов. Правило таково:

 E:first-child:nth-last-child(N), E:first-child:nth-last-child(N) ~ E { /* Styles for E when there are N elements in E's parent */ } 

Буквально этот селектор означает стиль первого дочернего элемента, который также является Nth-последним дочерним элементом, а также все элементы, следующие за ним в контейнере . Дублируя это правило, изменяя значение N, мы можем стилизовать элементы в зависимости от общего числа братьев и сестер. В нашем случае вот что мы можем придумать:

 .steps__item { /* Other styles... */ @for $i from 1 through 10 {sp &:first-child:nth-last-child(#{$i}), &:first-child:nth-last-child(#{$i}) ~ & { width: (100% / $i); } } } 

Этот милый маленький цикл дает этот CSS:

 .steps__item:first-child:nth-last-child(1), .steps__item:first-child:nth-last-child(1) ~ .steps__item { width: 100%; } .steps__item:first-child:nth-last-child(2), .steps__item:first-child:nth-last-child(2) ~ .steps__item { width: 50%; } .steps__item:first-child:nth-last-child(3), .steps__item:first-child:nth-last-child(3) ~ .steps__item { width: 33.33333%; } .steps__item:first-child:nth-last-child(4), .steps__item:first-child:nth-last-child(4) ~ .steps__item { width: 25%; } .steps__item:first-child:nth-last-child(5), .steps__item:first-child:nth-last-child(5) ~ .steps__item { width: 20%; } .steps__item:first-child:nth-last-child(6), .steps__item:first-child:nth-last-child(6) ~ .steps__item { width: 16.66667%; } .steps__item:first-child:nth-last-child(7), .steps__item:first-child:nth-last-child(7) ~ .steps__item { width: 14.28571%; } .steps__item:first-child:nth-last-child(8), .steps__item:first-child:nth-last-child(8) ~ .steps__item { width: 12.5%; } .steps__item:first-child:nth-last-child(9), .steps__item:first-child:nth-last-child(9) ~ .steps__item { width: 11.11111%; } .steps__item:first-child:nth-last-child(10), .steps__item:first-child:nth-last-child(10) ~ .steps__item { width: 10%; } 

Хорошо, это определенно приличное количество CSS, но это помогает! Да, как я уже говорил, велика вероятность того, что максимальное количество шагов в мастере шагов будет больше 5 или 6, а не 10 или больше.

Одна вещь, которую мы не должны забывать, это добавить левую границу к первому элементу и правую границу к последнему. Кроме того, последний элемент не должен иметь стрелку (по крайней мере, на мой взгляд). Для этого опять же два пути: либо мы используем псевдоклассы:

 .steps__item:first-of-type { border-left: $step-border; } .steps__item:last-of-type { border-right: $step-border; &:after { content: none; } } 

Или, если мы хотим избежать использования слишком сложных расширенных функций CSS для обеспечения обратной совместимости, я предлагаю добавить атрибуты класса в DOM:

 .steps__item--first { border-left: $step-border; } .steps__item--last { border-right: $step-border; &:after { content: none; } } 

шаг мастер-5

Каждый .steps__item обязательно содержит подэлемент; либо ссылка, когда раздел не отключен, либо span когда он есть. Этот элемент отвечает за стили содержимого: шрифт, цвет, выравнивание и т. Д.

 /** * Links * 1. Move to block to enlarge mouse area * 2. Center the label inside the item * 3. Remove the initial underline in case it's a `a` * 4. Add some extra space on top and bottom * 5. Visual sugar */ .steps__link { display: block; /* 1 */ text-align: center; /* 2 */ text-decoration: none; /* 3 */ padding: $step-baseline 0; /* 4 */ transition: .25s ease-out; /* 5 */ color: $step-text-color-disabled; /** * Hover/focus styles * Changing the text color is enough */ &:hover, &:focus { color: $step-text-color; } /** * Prevent any hover state on disabled items */ [disabled] &:hover, [disabled] &:focus { color: $step-text-color-disabled; } } 

Вместо того, чтобы устанавливать некоторые стили для состояния наведения / фокусировки, а затем переопределять их, когда шаг отключен, мы могли бы переписать его следующим образом, используя псевдокласс :not() (однако имейте в виду, что он имеет немного меньшую поддержку браузера ):

 .steps__link { /* Other styles... */ :not([disabled]) > &:hover, :not([disabled]) > &:focus { color: $step-text-color; } } 

На данный момент это должно быть где-то. Это все еще нуждается в некоторых дополнительных деталях, но по крайней мере это делает работу. Больше нет проблем с макетом, он доступен, читается и так далее.

шаг мастер-6

Добавление нумерации

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

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

 /** * Numbers * 1. Display the current `steps` counter value * 2. Make it inline-block so it can be styled as a block * 3. Size the it according to the baseline * 4. Horizontal centering * 5. Vertical centering * 6. Extra space around it * 7. Make it round * 8. Color stuff */ .steps__link { /* Other styles...* / &:before { content: counter(steps); /* 1 */ display: inline-block; /* 2 */ width: $step-baseline * 3; /* 3 */ height: $step-baseline * 3; /* 3 */ text-align: center; /* 4 */ line-height: $step-baseline * 3; /* 5 */ margin: 0 $step-baseline * 2; /* 6 */ border-radius: 50%; /* 7 */ background: $step-counter-color; /* 8 */ color: white; /* 8 */ } } 

шаг мастер-7

Добавление стрелок

Давайте добавим маленькие стрелки, используя псевдоэлементы! По сути, каждый элемент, кроме последнего, будет иметь псевдоэлемент в форме квадрата справа и повернут, чтобы выглядеть как стрелка. Немного сложно настроить, но мы сделаем это!

Теперь давайте начнем с математики, хорошо? Мы собираемся повернуть квадраты на 45 градусов, чтобы их углы были направлены сверху, справа, снизу и слева. Чтобы он выглядел правильно, нам нужно убедиться, что диагональ квадрата строго равна высоте элемента шага. И это предполагает некоторые расчеты.

Для начала посчитаем высоту элемента шага:

step_height = height of tallest content + twice the vertical padding + twice the border

Мы знаем, что самое высокое содержание — это число, которое мы только что добавили, и мы жестко закодировали его высоту, чтобы она была в три раза больше базовой линии. Мы знаем, что вертикальный отступ для ссылок равен базовой линии. И, наконец, у нас есть две границы 1px. Легко!

 $step-height: ($step-baseline * 3) + ($step-baseline * 2) + (1/16 * 1em * 2); 

Который оказывается 2.625em . Таким образом, мы точно знаем, что диагональ нашего 2.625em должна быть 2.625em однако мы не можем определить размер элемента по его диагонали, поэтому нам нужно вычислить длину его стороны по этому значению. Формула:

 side_length = diagonal * sqrt(2) / 2 

Большой! Если вы используете Compass или SassyMath , есть функция sqrt() , иначе нам нужно включить приблизительную оценку того, что на самом деле является квадратный корень из 2. Я пошел с: 1.4142135623730951 . Чтобы получить максимум от обоих миров, мы можем написать это так:

 $sqrt-2: if(function-exists('sqrt') == true, sqrt(2), 1.4142135623730951); 

Хорошо, мы получили все, давайте напишем код!

 /** * Arrows * 1. Sizing (yay math!) * 2. Putting it on the right edge * 3. Moving it back to the left from one half of it's size * 4. Slightly translating it from the top * 5. Rotating it 45 degrees, obviously * 6. Moving it on top of other things * 7. Applying borders on the two visible sides */ .steps__item { /* Other styles... */ &:after { $sqrt-2: if(function-exists('sqrt') == true, sqrt(2), 1.4142135623730951); $step-height: ($step-baseline * 3) + ($step-baseline * 2) + (1/16 * 1em * 2); $step-arrow-size: $step-height * $sqrt-2 / 2; $top-offset: 1 / $sqrt-2 / 2; content: ''; width: $step-arrow-size; /* 1 */ height: $step-arrow-size; /* 1 */ position: absolute; left: 100%; /* 2 */ margin-left: -$step-arrow-size / 2; /* 3 */ top: $top-offset; /* 4 */ transform: rotate(45deg); /* 5 */ z-index: 2; /* 6 */ background: inherit; border-right: $step-border; /* 7 */ border-top: $step-border; /* 7 */ } } 

шаг мастер-8

Работа с текущим шагом

Ничего не поделаешь, поэтому текущий шаг выглядит так, как будто он подсвечивается. Нам нужно только изменить цвет фона. Поскольку цвет псевдоэлементов зависит от цвета фона, все будет работать нормально.

 .steps__item--active { background: $step-background-color-active; } 

Мы также должны изменить цвет текста как на готовых, так и на активных шагах, а также на цвет фона номера. Маленькие вещи, но это то, что делает мага так приятно.

 .steps__item--done .steps__link, .steps__iten--active .steps__link { color: $step-text-color; &:before { background: $step-counter-color-active; } } 

Работа с маленькими экранами

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

 .steps__items { /* Other styles... */ @media (max-width: $step-breakpoint) { width: 100% !important; border: $step-border; border-bottom: none; padding: ($step-baseline * 2) 0; /** * Removing the arrows */ &:after { content: none; } } } .steps__item--last { /* Other styles... */ @media (max-width: $step-breakpoint) { border-bottom: $step-border; } } .steps__link:before { /* Other styles... */ /** * Moving numbers on the left rather than stuck to the content */ @media (max-width: $step-breakpoint) { float: left; margin-right: 0; } } 

Это оно!

шаг мастер-9

Работа со старыми браузерами

Чтобы иметь дело со старыми браузерами, есть два различных способа:

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

  • продвинутые селекторы CSS;
  • CSS трансформации;
  • сгенерированный контент (псевдоэлементы).

К сожалению, первый не может быть легко заполнен только CSS, поэтому я советую вам вообще избегать расширенных селекторов CSS, если вам нужно поддерживать устаревшие браузеры: просто добавьте больше классов в DOM.

Что касается CSS-преобразований, которые используются для стрелок, очень легко добавить решение, когда они не поддерживаются:

 // With Modernizr .no-csstransforms .steps__item { border-right: $step-border; &:after { content: none; } } // With Features Queries @supports not (transform: rotate(45deg)), not (-webkit-transform: rotate(45deg)), not (-ms-transform: rotate(45deg)) { .steps__item { border-right: $step-border; &:after { content: none; } } } 

И последнее, но не менее важное: сгенерированный контент: к сожалению, невозможно обнаружить поддержку сгенерированного контента для запросов функций, поэтому для исправления этого последнего укуса я предлагаю вам Modernizr.

 .no-generatedcontent .steps { list-style-type: decimal; list-style-position: inside; } 

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

шаг мастер-10

Последние мысли

Вот и все, ребята! Надеюсь, вам понравилось объяснение, и если вы можете придумать что-нибудь, чтобы улучшить его, обязательно поделитесь!