Создание компонента вкладок

Базовый обзор того, как создать компонент вкладок, аналогичный тем, которые есть в приложениях для iOS и Android.

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

Демо

Если вы предпочитаете видео, вот версия этого поста на YouTube:

Обзор

Вкладки являются распространенным компонентом дизайн-систем, но могут принимать самые разные формы. Сначала были вкладки рабочего стола, построенные на элементе <frame> , а теперь у нас есть удобные мобильные компоненты, которые анимируют контент на основе физических свойств. Все они пытаются сделать одно и то же: сэкономить место.

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

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

Веб-тактика

В целом этот компонент показался мне довольно простым в создании благодаря нескольким важным функциям веб-платформы:

  • scroll-snap-points для элегантного взаимодействия с помощью смахивания и клавиатуры с соответствующими позициями остановки прокрутки
  • Глубокие ссылки через хэши URL-адресов для поддержки привязки прокрутки на странице и поддержки совместного использования браузером.
  • Поддержка чтения с экрана с разметкой элементов <a> и id="#hash"
  • prefers-reduced-motion для включения плавных переходов и мгновенной прокрутки на странице.
  • Встроенная веб-функция @scroll-timeline для динамического подчеркивания и изменения цвета выбранной вкладки.

HTML

По сути, UX здесь таков: щелкните ссылку, URL-адрес будет представлять состояние вложенной страницы, а затем вы увидите обновление области контента по мере прокрутки браузера к соответствующему элементу.

Здесь есть некоторые элементы структурного контента: ссылки и :target . Нам нужен список ссылок, для которых отлично подходит <nav> , и список элементов <article> , для которых отлично подходит <section> . Каждый хеш ссылки будет соответствовать разделу, позволяя браузеру прокручивать элементы посредством привязки.

При нажатии кнопки ссылки происходит переход к конкретному контенту.

Например, при нажатии на ссылку автоматически фокусируется статья :target в Chrome 89, JS не требуется. Затем пользователь может, как всегда, прокручивать содержимое статьи с помощью своего устройства ввода. Как указано в разметке, это бесплатный контент.

Для организации вкладок я использовал следующую разметку:

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

Я могу установить связи между элементами <a> и <article> с помощью свойств href и id следующим образом:

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

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

Прокрутка макетов

В этом компоненте есть 3 различных типа областей прокрутки:

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

Существует два разных типа элементов, участвующих в прокрутке:

  1. Окно
    Поле с определенными размерами, имеющее стиль свойства overflow .
  2. Негабаритная поверхность
    В этом макете это контейнеры списков: навигационные ссылки, статьи разделов и содержимое статей.

Макет <snap-tabs>

Макет верхнего уровня, который я выбрал, был гибким (Flexbox). Я установил направление column , чтобы заголовок и раздел располагались вертикально. Это наше первое окно прокрутки, и оно скрывает все, а переполнение скрыто. В заголовке и разделе скоро будет использоваться прокрутка, как в отдельных зонах.

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

Возвращаясь к красочной диаграмме с тремя прокрутками:

  • <header> теперь готов стать (розовым) контейнером прокрутки.
  • <section> подготовлен как (синий) контейнер прокрутки.

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

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

Макет вкладок <header>

Следующий макет почти такой же: я использую flex для создания вертикального порядка.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

.snap-indicator должен перемещаться горизонтально вместе с группой ссылок, и этот макет заголовка помогает подготовить этот этап. Здесь нет абсолютно позиционированных элементов!

элементы nav и span.indicator имеют наложения ярко-розового цвета, обозначающие пространство, которое они занимают в компоненте.

Далее стили прокрутки. Оказывается, мы можем использовать стили прокрутки для двух наших горизонтальных областей прокрутки (заголовок и раздел), поэтому я создал служебный класс .scroll-snap-x .

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

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

Макет заголовка вкладок <nav>

Навигационные ссылки должны располагаться в линию, без разрывов строк, по центру по вертикали, и каждый элемент ссылки должен быть привязан к контейнеру привязки прокрутки. Swift работает для CSS 2021 года!

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

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

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

Макет вкладок <section> раздел>

Этот раздел является гибким элементом и должен быть доминирующим потребителем пространства. Также необходимо создать столбцы для размещения статей. И снова быстрая работа над CSS 2021! block-size: 100% растягивает этот элемент, чтобы максимально заполнить родительский элемент, а затем для собственного макета он создает серию столбцов, ширина которых составляет 100% ширины родительского элемента. Проценты здесь отлично работают, потому что мы наложили строгие ограничения на родительский элемент.

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

Это как если бы мы говорили: «Расширяйтесь по вертикали настолько, насколько это возможно, настойчиво» (помните заголовок, который мы установили для flex-shrink: 0 : это защита от этого расширения), который устанавливает высоту строки для набор колонн во всю высоту. Стиль auto-flow предписывает сетке всегда располагать дочерние элементы в горизонтальной линии, без переноса, именно так, как мы хотим; для переполнения родительского окна.

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

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

Макет вкладок <article>

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

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

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

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

Статья является дочерним элементом сетки, и ее размер заранее определен как область просмотра, которую мы хотим предоставить для прокрутки. Это означает, что мне не нужны стили высоты или ширины, мне просто нужно определить, как происходит переполнение. Я установил для overflow-y значение auto, а затем также перехватываю взаимодействие прокрутки с помощью удобного свойства overscroll-behavior.

Обзор трех областей прокрутки

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

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

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

DevTools может помочь нам визуализировать это:

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

Макеты прокрутки готовы: привязка, глубокая связь и доступ с клавиатуры. Прочная основа для улучшения UX, стиля и удовольствия.

Основные характеристики

Дочерние элементы, привязанные к прокрутке, сохраняют свое заблокированное положение во время изменения размера. Это означает, что JavaScript не нужно будет отображать что-либо при повороте устройства или изменении размера браузера. Попробуйте это в режиме устройства Chromium DevTools, выбрав любой режим, кроме Responsive , а затем изменив размер рамки устройства. Обратите внимание, что элемент остается в поле зрения и заблокирован со своим содержимым. Это стало доступно с тех пор, как Chromium обновил свою реализацию, чтобы она соответствовала спецификации. Вот сообщение в блоге об этом.

Анимация

Цель работы по анимации — четко связать взаимодействие с обратной связью пользовательского интерфейса. Это помогает пользователю (надеюсь) беспрепятственно открыть весь контент. Я буду добавлять движение целенаправленно и условно. Теперь пользователи могут указывать свои предпочтения в движении в своей операционной системе, и мне очень нравится реагировать на их предпочтения в своих интерфейсах.

Я свяжу подчеркивание табуляции с позицией прокрутки статьи. Привязка не только обеспечивает хорошее выравнивание, но и закрепляет начало и конец анимации. Это сохраняет <nav> , который действует как мини-карта , связанным с содержимым. Мы будем проверять предпочтения пользователя в отношении движения как с помощью CSS, так и с помощью JS. Есть несколько замечательных мест, на которые стоит обратить внимание!

Поведение прокрутки

Существует возможность улучшить поведение при движении как :target , так и element.scrollIntoView() . По умолчанию это мгновенно. Браузер просто устанавливает положение прокрутки. А что, если мы захотим перейти в эту позицию прокрутки, а не моргать там?

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

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

Индикатор вкладок

Цель этой анимации — помочь связать индикатор с состоянием контента. Я решил раскрасить стили кроссфейдной border-bottom для пользователей, которые предпочитают ограниченное движение, а также анимацию скольжения и затухания, связанную с прокруткой, для пользователей, которых устраивает движение.

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

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

Я скрываю .snap-indicator когда пользователь предпочитает ограниченное движение, поскольку он мне больше не нужен. Затем я заменяю его стилями border-block-end и transition . Также обратите внимание при взаимодействии вкладок, что активный элемент навигации не только имеет подчеркивание бренда, но и цвет текста становится темнее. Активный элемент имеет более высокий цветовой контраст текста и яркий акцент подсветки.

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

@scroll-timeline

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

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);

Сначала я проверяю предпочтения пользователя в отношении движения с помощью JavaScript. Если результатом этого является false , что означает, что пользователь предпочитает ограниченное движение, мы не будем запускать какие-либо эффекты движения, связывающие прокрутку.

if (motionOK) {
  // motion based animation code
}

На момент написания этой статьи поддержка @scroll-timeline браузером отсутствует. Это черновая спецификация , содержащая только экспериментальные реализации. Однако у него есть полифилл, который я использую в этой демонстрации.

ScrollTimeline

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

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

Я хочу, чтобы одна вещь следовала за позицией прокрутки другой, и, создавая ScrollTimeline я определяю драйвер ссылки прокрутки, scrollSource . Обычно анимация в Интернете выполняется в соответствии с глобальным интервалом времени, но с помощью специальной sectionScrollTimeline в памяти я могу все это изменить.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

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

Динамические ключевые кадры

Существует действительно мощный чисто декларативный CSS-способ анимации с помощью @scroll-timeline , но анимация, которую я выбрал, была слишком динамичной. Невозможно переключаться между auto шириной и нет способа динамически создавать несколько ключевых кадров в зависимости от длины дочерних элементов.

Однако JavaScript знает, как получить эту информацию, поэтому мы сами будем перебирать дочерние элементы и получать вычисленные значения во время выполнения:

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Для каждого tabnavitem деструктурируйте позицию offsetLeft и верните строку, которая использует ее в качестве значения translateX . Это создаст 4 ключевых кадра преобразования для анимации. То же самое делается и с шириной: каждому задается вопрос, какова его динамическая ширина, а затем она используется в качестве значения ключевого кадра.

Вот пример вывода, основанный на моих шрифтах и ​​настройках браузера:

Ключевые кадры TranslateX:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

Ширина ключевых кадров:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

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

активная и неактивная вкладки отображаются с наложениями VisBug, которые показывают проходные оценки контрастности для обеих

Пользователь управляет анимацией своим взаимодействием, видя, как ширина и положение индикатора меняются от одного раздела к другому, идеально отслеживая прокрутку.

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

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

Вот как я это сделал:

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

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

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

Ключевой кадр с цветом var(--text-active-color) выделяет ссылку, в остальном это стандартный цвет текста. Вложенный цикл делает это относительно простым, поскольку внешний цикл — это каждый элемент навигации, а внутренний цикл — персональные ключевые кадры каждого элемента навигации. Я проверяю, совпадает ли элемент внешнего цикла с элементом внутреннего цикла, и использую это, чтобы узнать, когда он выбран.

Мне было очень весело писать это. Так много.

Еще больше улучшений JavaScript

Стоит напомнить, что суть того, что я вам здесь показываю, работает без JavaScript. С учетом сказанного, давайте посмотрим, как мы можем улучшить его, когда будет доступен JS.

Глубокие ссылки — это скорее мобильный термин, но я думаю, что цель глубокой ссылки здесь достигается с помощью вкладок, поскольку вы можете поделиться URL-адресом непосредственно с содержимым вкладки. Браузер на странице перейдет к идентификатору, который соответствует хешу URL-адреса. Я обнаружил, что этот обработчик onload оказывает влияние на разные платформы.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

Синхронизация завершения прокрутки

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

Вот как я жду окончания прокрутки: js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

Всякий раз, когда разделы прокручиваются, очистите тайм-аут раздела, если он есть, и начните новый. Когда разделы перестают прокручиваться, не очищайте тайм-аут и запускайте 100 мс после отдыха. Когда он сработает, вызовите функцию, которая пытается выяснить, где остановился пользователь.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

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

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

Установка активной вкладки начинается с очистки любой активной в данный момент вкладки, а затем присвоения входящему элементу навигации атрибута активного состояния. Вызов scrollIntoView() имеет забавное взаимодействие с CSS, на которое стоит обратить внимание.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

В CSS-утилиту привязки горизонтальной прокрутки мы вложили медиа-запрос, который применяет smooth прокрутку, если пользователь терпим к движению. JavaScript может свободно выполнять вызовы для прокрутки элементов в поле зрения, а CSS может декларативно управлять UX. Иногда они составляют очаровательную пару.

Заключение

Теперь, когда вы знаете, как я это сделал, как бы вы поступили?! Это создает забавную компонентную архитектуру! Кто собирается сделать 1-ю версию со слотами на любимом фреймворке? 🙂

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

Ремиксы сообщества

,

Базовый обзор того, как создать компонент вкладок, аналогичный тем, которые есть в приложениях для iOS и Android.

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

Демо

Если вы предпочитаете видео, вот версия этого поста на YouTube:

Обзор

Вкладки являются распространенным компонентом дизайн-систем, но могут принимать самые разные формы. Сначала были вкладки рабочего стола, построенные на элементе <frame> , а теперь у нас есть удобные мобильные компоненты, которые анимируют контент на основе физических свойств. Все они пытаются сделать одно и то же: сэкономить место.

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

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

Веб-тактика

В целом этот компонент показался мне довольно простым в создании благодаря нескольким важным функциям веб-платформы:

  • scroll-snap-points для элегантного взаимодействия с помощью смахивания и клавиатуры с соответствующими положениями остановки прокрутки
  • Глубокие ссылки через хэши URL-адресов для поддержки привязки прокрутки на странице и поддержки совместного использования браузером.
  • Поддержка чтения с экрана с разметкой элементов <a> и id="#hash"
  • prefers-reduced-motion для включения плавных переходов и мгновенной прокрутки на странице.
  • Встроенная веб-функция @scroll-timeline для динамического подчеркивания и изменения цвета выбранной вкладки.

HTML

По сути, UX здесь таков: щелкните ссылку, URL-адрес будет представлять состояние вложенной страницы, а затем вы увидите обновление области контента по мере прокрутки браузера к соответствующему элементу.

Здесь есть некоторые элементы структурного контента: ссылки и :target . Нам нужен список ссылок, для которых отлично подходит <nav> , и список элементов <article> , для которых отлично подходит <section> . Каждый хеш ссылки будет соответствовать разделу, позволяя браузеру прокручивать элементы посредством привязки.

При нажатии кнопки ссылки происходит переход к конкретному контенту.

Например, при нажатии на ссылку автоматически фокусируется статья :target в Chrome 89, JS не требуется. Затем пользователь может, как всегда, прокручивать содержимое статьи с помощью своего устройства ввода. Как указано в разметке, это бесплатный контент.

Для организации вкладок я использовал следующую разметку:

<snap-tabs>
  <header>
    <nav>
      <a></a>
      <a></a>
      <a></a>
      <a></a>
    </nav>
  </header>
  <section>
    <article></article>
    <article></article>
    <article></article>
    <article></article>
  </section>
</snap-tabs>

Я могу установить связи между элементами <a> и <article> с помощью свойств href и id следующим образом:

<snap-tabs>
  <header>
    <nav>
      <a href="#responsive"></a>
      <a href="#accessible"></a>
      <a href="#overscroll"></a>
      <a href="#more"></a>
    </nav>
  </header>
  <section>
    <article id="responsive"></article>
    <article id="accessible"></article>
    <article id="overscroll"></article>
    <article id="more"></article>
  </section>
</snap-tabs>

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

Прокрутка макетов

В этом компоненте есть 3 различных типа областей прокрутки:

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

Существует два разных типа элементов, участвующих в прокрутке:

  1. Окно
    Поле с определенными размерами, имеющее стиль свойства overflow .
  2. Негабаритная поверхность
    В этом макете это контейнеры списков: навигационные ссылки, статьи разделов и содержимое статей.

Макет <snap-tabs>

Макет верхнего уровня, который я выбрал, был гибким (Flexbox). Я установил направление column , чтобы заголовок и раздел располагались вертикально. Это наше первое окно прокрутки, и оно скрывает все, а переполнение скрыто. В заголовке и разделе скоро будет использоваться прокрутка, как в отдельных зонах.

HTML
<snap-tabs>
  <header></header>
  <section></section>
</snap-tabs>
CSS
  snap-tabs {
  display: flex;
  flex-direction: column;

  /* establish primary containing box */
  overflow: hidden;
  position: relative;

  & > section {
    /* be pushy about consuming all space */
    block-size: 100%;
  }

  & > header {
    /* defend against 
needing 100% */ flex-shrink: 0; /* fixes cross browser quarks */ min-block-size: fit-content; } }

Возвращаясь к красочной диаграмме с тремя прокрутками:

  • <header> теперь готов стать (розовым) контейнером прокрутки.
  • <section> подготовлен как (синий) контейнер прокрутки.

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

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

Макет вкладок <header>

Следующий макет почти такой же: я использую flex для создания вертикального порядка.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

.snap-indicator должен перемещаться горизонтально вместе с группой ссылок, и этот макет заголовка помогает подготовить этот этап. Здесь нет абсолютно позиционированных элементов!

элементы nav и span.indicator имеют наложения ярко-розового цвета, обозначающие пространство, которое они занимают в компоненте.

Далее стили прокрутки. Оказывается, мы можем использовать стили прокрутки для двух наших горизонтальных областей прокрутки (заголовок и раздел), поэтому я создал служебный класс .scroll-snap-x .

.scroll-snap-x {
  /* browser decide if x is ok to scroll and show bars on, y hidden */
  overflow: auto hidden;
  /* prevent scroll chaining on x scroll */
  overscroll-behavior-x: contain;
  /* scrolling should snap children on x */
  scroll-snap-type: x mandatory;

  @media (hover: none) {
    scrollbar-width: none;

    &::-webkit-scrollbar {
      width: 0;
      height: 0;
    }
  }
}

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

Макет заголовка вкладок <nav>

Навигационные ссылки должны быть расположены в одну линию, без разрывов строк, по центру по вертикали, и каждый элемент ссылки должен быть привязан к контейнеру привязки прокрутки. Swift работает для CSS 2021 года!

HTML
<nav>
  <a></a>
  <a></a>
  <a></a>
  <a></a>
</nav>
CSS
  nav {
  display: flex;

  & a {
    scroll-snap-align: start;

    display: inline-flex;
    align-items: center;
    white-space: nowrap;
  }
}

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

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

Макет вкладок <section> раздел>

Этот раздел является гибким элементом и должен быть доминирующим потребителем пространства. Также необходимо создать столбцы для размещения статей. И снова быстрая работа над CSS 2021! block-size: 100% растягивает этот элемент, чтобы максимально заполнить родительский элемент, а затем для собственного макета он создает серию столбцов, ширина которых составляет 100% ширины родительского элемента. Проценты здесь отлично работают, потому что мы наложили строгие ограничения на родительский элемент.

HTML
<section>
  <article></article>
  <article></article>
  <article></article>
  <article></article>
</section>
CSS
  section {
  block-size: 100%;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
}

Это как если бы мы говорили: «Расширяйтесь по вертикали настолько, насколько это возможно, настойчиво» (помните заголовок, который мы установили для flex-shrink: 0 : это защита от этого расширения), который устанавливает высоту строки для набор колонн во всю высоту. Стиль auto-flow предписывает сетке всегда располагать дочерние элементы в горизонтальной линии, без переноса, именно так, как мы хотим; для переполнения родительского окна.

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

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

Макет вкладок <article>

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

HTML
<article>
  <h2></h2>
  <p></p>
  <p></p>
  <h2></h2>
  <p></p>
  <p></p>
  ...
</article>
CSS
article {
  scroll-snap-align: start;

  overflow-y: auto;
  overscroll-behavior-y: contain;
}

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

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

Статья является дочерним элементом сетки, и ее размер заранее определен как область просмотра, которую мы хотим предоставить для прокрутки. Это означает, что мне здесь не нужны стили высоты или ширины, мне просто нужно определить, как происходит переполнение. Я устанавливаю для overflow-y значение auto, а затем также фиксирую взаимодействие прокрутки с помощью удобного свойства overscroll-behavior.

Обзор трех областей прокрутки

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

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

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

DevTools может помочь нам визуализировать это:

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

Макеты прокрутки готовы: привязка, глубокая связь и доступ с клавиатуры. Прочная основа для улучшения UX, стиля и удовольствия.

Основные характеристики

Дочерние элементы, привязанные к прокрутке, сохраняют свое заблокированное положение во время изменения размера. Это означает, что JavaScript не нужно будет отображать что-либо при повороте устройства или изменении размера браузера. Попробуйте это в режиме устройства Chromium DevTools, выбрав любой режим, кроме Responsive , а затем изменив размер рамки устройства. Обратите внимание, что элемент остается в поле зрения и заблокирован своим содержимым. Это стало доступно с тех пор, как Chromium обновил свою реализацию, чтобы она соответствовала спецификации. Вот сообщение в блоге об этом.

Анимация

Цель работы по анимации — четко связать взаимодействие с обратной связью пользовательского интерфейса. Это помогает пользователю (надеюсь) беспрепятственно открыть весь контент. Я буду добавлять движение целенаправленно и условно. Теперь пользователи могут указывать свои предпочтения в движении в своей операционной системе, и мне очень нравится реагировать на их предпочтения в своих интерфейсах.

Я свяжу подчеркивание табуляции с позицией прокрутки статьи. Привязка не только обеспечивает хорошее выравнивание, но и закрепляет начало и конец анимации. Это сохраняет <nav> , который действует как мини-карта , связанным с содержимым. Мы будем проверять предпочтения пользователя в отношении движения как с помощью CSS, так и с помощью JS. Есть несколько замечательных мест, на которые стоит обратить внимание!

Поведение прокрутки

Существует возможность улучшить поведение при движении как :target , так и element.scrollIntoView() . По умолчанию это мгновенно. Браузер просто устанавливает положение прокрутки. А что, если мы захотим перейти в эту позицию прокрутки, а не моргать там?

@media (prefers-reduced-motion: no-preference) {
  .scroll-snap-x {
    scroll-behavior: smooth;
  }
}

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

Индикатор вкладок

Цель этой анимации — помочь связать индикатор с состоянием контента. Я решил раскрасить стили кроссфейдной border-bottom для пользователей, которые предпочитают ограниченное движение, а также анимацию скольжения и затухания, связанную с прокруткой, для пользователей, которых устраивает движение.

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

@media (prefers-reduced-motion: reduce) {
  snap-tabs > header a {
    border-block-end: var(--indicator-size) solid hsl(var(--accent) / 0%);
    transition: color .7s ease, border-color .5s ease;

    &:is(:target,:active,[active]) {
      color: var(--text-active-color);
      border-block-end-color: hsl(var(--accent));
    }
  }

  snap-tabs .snap-indicator {
    visibility: hidden;
  }
}

Я скрываю .snap-indicator когда пользователь предпочитает ограниченное движение, поскольку он мне больше не нужен. Затем я заменяю его стилями border-block-end и transition . Также обратите внимание при взаимодействии вкладок, что активный элемент навигации не только имеет подчеркивание бренда, но и цвет текста становится темнее. Активный элемент имеет более высокий цветовой контраст текста и яркий акцент подсветки.

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

@scroll-timeline

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

const { matches:motionOK } = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
);

Сначала я проверяю предпочтения пользователя в отношении движения с помощью JavaScript. Если результатом этого является false , что означает, что пользователь предпочитает ограниченное движение, мы не будем запускать какие-либо эффекты движения, связывающие прокрутку.

if (motionOK) {
  // motion based animation code
}

На момент написания этой статьи поддержка @scroll-timeline браузером отсутствует. Это черновая спецификация , содержащая только экспериментальные реализации. Однако у него есть полифилл, который я использую в этой демонстрации.

ScrollTimeline

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

const sectionScrollTimeline = new ScrollTimeline({
  scrollSource: tabsection,  // snap-tabs > section
  orientation: 'inline',     // scroll in the direction letters flow
  fill: 'both',              // bi-directional linking
});

Я хочу, чтобы одна вещь следовала за позицией прокрутки другой, и, создавая ScrollTimeline я определяю драйвер ссылки прокрутки, scrollSource . Обычно анимация в Интернете выполняется в соответствии с глобальным интервалом времени, но с помощью специальной sectionScrollTimeline в памяти я могу все это изменить.

tabindicator.animate({
    transform: ...,
    width: ...,
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

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

Динамические ключевые кадры

Существует действительно мощный способ чистого декларативного CSS, чтобы оживить с помощью @scroll-timeline , но анимация, которую я решил сделать, была слишком динамичной. Невозможно переходить между auto шириной, и нет никакого способа динамически создать ряд ключевых кадров на основе длины детей.

JavaScript знает, как получить эту информацию, поэтому мы сами перевернем детей и возьмем вычисленные значения во время выполнения:

tabindicator.animate({
    transform: [...tabnavitems].map(({offsetLeft}) =>
      `translateX(${offsetLeft}px)`),
    width: [...tabnavitems].map(({offsetWidth}) =>
      `${offsetWidth}px`)
  }, {
    duration: 1000,
    fill: 'both',
    timeline: sectionScrollTimeline,
  }
);

Для каждого tabnavitem деструктуруйте позицию offsetLeft и возвращайте строку, которая использует ее в качестве значения translateX . Это создает 4 преобразования ключевых кадров для анимации. То же самое сделано для ширины, каждый спрашивает, какова его динамическая ширина, а затем используется в качестве значения ключа.

Вот пример вывода, основанный на моих шрифтах и ​​предпочтениях браузера:

Translatex KeyFrames:

[...tabnavitems].map(({offsetLeft}) =>
  `translateX(${offsetLeft}px)`)

// results in 4 array items, which represent 4 keyframe states
// ["translateX(0px)", "translateX(121px)", "translateX(238px)", "translateX(464px)"]

Ключевые кадры ширины:

[...tabnavitems].map(({offsetWidth}) =>
  `${offsetWidth}px`)

// results in 4 array items, which represent 4 keyframe states
// ["121px", "117px", "226px", "67px"]

Подводя итог стратегии, индикатор TAB теперь оживит 4 -й ключевой кафедры в зависимости от положения Scroll Snap Screct Scroller. Точки SNAP создают четкое разграничение между нашими ключевыми кадром и действительно добавляют синхронизированное ощущение анимации.

Активная вкладка и неактивная вкладка отображаются с наложениями Visbug, которые показывают передачи контрастных показателей для обоих

Пользователь ведет анимацию со своим взаимодействием, видя ширину и положение изменения индикатора от одного раздела на другой, идеально отслеживая прокрутку.

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

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

Вот как я это сделал:

tabnavitems.forEach(navitem => {
  navitem.animate({
      color: [...tabnavitems].map(item =>
        item === navitem
          ? `var(--text-active-color)`
          : `var(--text-color)`)
    }, {
      duration: 1000,
      fill: 'both',
      timeline: sectionScrollTimeline,
    }
  );
});

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

[...tabnavitems].map(item =>
  item === navitem
    ? `var(--text-active-color)`
    : `var(--text-color)`)

// results in 4 array items, which represent 4 keyframe states
// [
  "var(--text-active-color)",
  "var(--text-color)",
  "var(--text-color)",
  "var(--text-color)",
]

Ключевой кадр с цветом var(--text-active-color) выделяет ссылку, и в противном случае это стандартный цвет текста. Вложенная петля делает его относительно простым, так как внешний цикл является каждым элементом NAV, а внутренний цикл - это личные ключевые кадры каждого Navitem. Я проверяю, такой же элемент внешнего цикла, как и внутренний цикл, и использую его, чтобы знать, когда он выбран.

Мне было очень весело писать это. Так много.

Еще больше улучшений JavaScript

Стоит напоминать о том, что ядро ​​того, что я показываю вам здесь, работает без JavaScript. С учетом сказанного, давайте посмотрим, как мы можем улучшить его, когда JS доступен.

Глубокие ссылки - это скорее мобильный термин, но я думаю, что намерение глубокой ссылки встречается здесь с вкладками в том смысле, что вы можете поделиться URL непосредственно на содержимое вкладки. Браузер будет перейти к идентификатору, который сочетается с URL-хэшем. Я обнаружил, что этот обработчик onload сделал эффект между платформами.

window.onload = () => {
  if (location.hash) {
    tabsection.scrollLeft = document
      .querySelector(location.hash)
      .offsetLeft;
  }
}

Прокрутите конец синхронизации

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

Вот как я жду, пока конец прокрутки: js tabsection.addEventListener('scroll', () => { clearTimeout(tabsection.scrollEndTimer); tabsection.scrollEndTimer = setTimeout(determineActiveTabSection, 100); });

Всякий раз, когда разделы прокручиваются, очистите тайм -аут раздела, если там, и запускайте новый. Когда секции перестают прокручивать, не очищайте тайм -аут и стреляйте в 100 мс после отдыха. Когда он стреляет, вызовите функцию, которая стремится выяснить, где пользователь остановился.

const determineActiveTabSection = () => {
  const i = tabsection.scrollLeft / tabsection.clientWidth;
  const matchingNavItem = tabnavitems[i];

  matchingNavItem && setActiveTab(matchingNavItem);
};

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

const setActiveTab = tabbtn => {
  tabnav
    .querySelector(':scope a[active]')
    .removeAttribute('active');

  tabbtn.setAttribute('active', '');
  tabbtn.scrollIntoView();
};

Установка вкладки Active начинается с очистки любой вкладки Active, а затем предоставив элемент входящего навигации атрибут Active State. Призыв к scrollIntoView() имеет забавное взаимодействие с CSS, которое стоит отметить.

.scroll-snap-x {
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;

  @media (prefers-reduced-motion: no-preference) {
    scroll-behavior: smooth;
  }
}

В CSS CSS горизонтальной прокрутки CSS мы вложили медиа -запрос, который применяет smooth прокрутку, если пользователь устойчив к движению. JavaScript может свободно делать вызовы для прокрутки элементов, и CSS может управлять UX декларативно. Довольно восхитительный маленький матч, который они иногда делают.

Заключение

Теперь, когда вы знаете, как я это сделал, как бы вы?! Это делает для какой -то забавной архитектуры компонентов! Кто сделает 1 -ю версию с слотами в своих любимых рамках? 🙂

Давайте диверсифицируем наши подходы и узнаем все способы построения в Интернете. Создайте сбой , напишите мне свою версию, и я добавлю его в раздел «Ремикс сообщества» ниже.

Общественные ремиксы