Создание компонента с множественным выбором

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

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

Демо

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

Обзор

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

Взаимодействия

Цель состоит в том, чтобы обеспечить быстрый обход параметров фильтра для всех пользователей и их различных типов ввода. Это будет реализовано с помощью пары адаптируемых и отзывчивых компонентов. Традиционная боковая панель с флажками для рабочего стола, клавиатуры и программ чтения с экрана, а также <select multiple> для сенсорных пользователей.

Сравнительный снимок экрана, показывающий светлый и темный рабочий стол с боковой панелью флажков и мобильными устройствами iOS и Android с элементом множественного выбора.

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

Трогать

Сенсорный компонент экономит место и повышает точность взаимодействия с пользователем на мобильных устройствах. Он экономит место за счет свертывания всей боковой панели флажков во встроенный сенсорный наложенный элемент <select> . Это повышает точность ввода, демонстрируя большое количество сенсорных наложений, предоставляемых системой.

Предварительный просмотр скриншота элемента с множественным выбором в Chrome на Android, iPhone и iPad. На iPad и iPhone включен множественный выбор, и каждый из них получает уникальные возможности, оптимизированные для размера экрана.

Клавиатура и геймпад

Ниже приведена демонстрация того, как использовать <select multiple> с клавиатуры.

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

Разметка

Оба компонента будут содержаться в одном элементе <form> . Результаты этой формы, будь то флажки или множественный выбор, будут отслеживаться и использоваться для фильтрации сетки, но также могут быть отправлены на сервер.

<form>

</form>

Компонент флажков

Группы флажков должны быть заключены в элемент <fieldset> и иметь <legend> . Когда HTML структурирован таким образом, программы чтения с экрана и FormData автоматически понимают взаимосвязь элементов.

<form>
  <fieldset>
    <legend>New</legend>
    … checkboxes …
  </fieldset>
</form>

Создав группировку, добавьте <label> и <input type="checkbox"> для каждого фильтра. Я решил обернуть свой элемент в <div> , чтобы свойство CSS- gap могло распределять их равномерно и сохранять выравнивание, когда метки становятся многострочными.

<form>
  <fieldset>
    <legend>New</legend>
    <div>
      <input type="checkbox" id="last 30 days" name="new" value="last 30 days">
      <label for="last 30 days">Last 30 Days</label>
    </div>
    <div>
      <input type="checkbox" id="last 6 months" name="new" value="last 6 months">
      <label for="last 6 months">Last 6 Months</label>
    </div>
   </fieldset>
</form>

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

<select multiple> компонент

Редко используемая функция элемента <select>multiple . Когда атрибут используется с элементом <select> , пользователю разрешено выбирать многие элементы из списка. Это похоже на изменение взаимодействия со списка радио на список флажков.

<form>
  <select multiple="true" title="Filter results by category">
    …
  </select>
</form>

Чтобы пометить и создать группы внутри <select> , используйте элемент <optgroup> и присвойте ему атрибут label и значение. Этот элемент и значение атрибута аналогичны элементам <fieldset> и <legend> .

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      …
    </optgroup>
  </select>
</form>

Теперь добавьте элементы <option> для фильтра.

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      <option value="last 30 days">Last 30 Days</option>
      <option value="last 6 months">Last 6 Months</option>
    </optgroup>
  </select>
</form>

Снимок экрана с изображением рабочего стола элемента с множественным выбором.

Отслеживание входных данных с помощью счетчиков для информирования вспомогательных технологий

В этом пользовательском интерфейсе используется метод роли статуса для отслеживания и ведения учета фильтров для программ чтения с экрана и других вспомогательных технологий. Видео на YouTube демонстрирует эту функцию. Интеграция начинается с HTML и атрибута role="status" .

<div role="status" class="sr-only" id="applied-filters"></div>

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

aside {
  counter-reset: filters;
}

По умолчанию счетчик будет равен 0 , и это здорово, в этом проекте по умолчанию ничего не :checked .

Далее, чтобы увеличить наш вновь созданный счетчик, мы нацелимся на дочерние элементы элемента <aside> , которые отмечены :checked . Когда пользователь меняет состояние входов, счетчик filters будет подсчитываться.

aside :checked {
  counter-increment: filters;
}

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

aside #applied-filters::before {
  content: counter(filters) " filters ";
}

HTML-код элемента роли статуса теперь будет сообщать программе чтения с экрана «2 фильтра». Это хорошее начало, но мы можем добиться большего, например поделиться результатами, обновленными фильтрами. Мы проделаем эту работу с помощью JavaScript, поскольку счетчики не могут этого сделать.

Скриншот программы чтения с экрана MacOS, на которой указано количество активных фильтров.

Гнездовой азарт

Алгоритм счетчиков отлично сочетался с CSSnesting-1 , поскольку мне удалось поместить всю логику в один блок. Ощущается портативным и централизованным для чтения и обновления.

aside {
  counter-reset: filters;

  & :checked {
    counter-increment: filters;
  }

  & #applied-filters::before {
    content: counter(filters) " filters ";
  }
}

Макеты

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

Форма

Чтобы оптимизировать удобочитаемость и удобство сканирования для пользователей, максимальная ширина формы составляет 30 символов, что фактически устанавливает оптическую ширину линии для каждой метки фильтра. В форме используется сетка и свойство gap для разделения наборов полей.

form {
  display: grid;
  gap: 2ch;
  max-inline-size: 30ch;
}

Элемент <select>

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

@media (pointer: coarse) {
  select[multiple] {
    display: block;
  }
}

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

Наборы полей

Стиль и макет по умолчанию для <fieldset> с <legend> уникальны:

Снимок экрана со стилями по умолчанию для набора полей и легенды.

Обычно для разделения дочерних элементов я бы использовал свойство gap , но уникальное расположение <legend> затрудняет создание равномерного набора дочерних элементов. Вместо gap используются соседний одноуровневый селектор и margin-block-start .

fieldset {
  padding: 2ch;

  & > div + div {
    margin-block-start: 2ch;
  }
}

Это исключает возможность регулировки пространства <legend> , ориентируясь только на дочерние элементы <div> .

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

Метка фильтра и флажок

Поскольку он является прямым дочерним элементом <fieldset> и находится в пределах максимальной ширины 30ch формы, текст метки может переноситься, если он слишком длинный. Перенос текста — это хорошо, но несовпадение текста и флажка — нет. Flexbox идеально подходит для этого.

fieldset > div {
  display: flex;
  gap: 2ch;
  align-items: baseline;
}
Снимок экрана, показывающий, как галочка выравнивается по первой строке текста в сценарии многострочного переноса.
Играйте больше в этом Codepen

Анимированная сетка

Анимацию макета выполняет Isotope . Производительный и мощный плагин для интерактивной сортировки и фильтрации.

JavaScript

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

Нормализация пользовательского ввода

Этот дизайн имеет одну форму с двумя разными способами ввода данных, и они не сериализуются одинаково. Однако с помощью JavaScript мы можем нормализовать данные.

Снимок экрана консоли DevTools JavaScript, на которой показаны цель и результаты нормализованных данных.

Я решил привести структуру данных элемента <select> в соответствие со структурой сгруппированных флажков. Для этого к элементу <select> добавляется прослушиватель событий input , после чего сопоставляются его selectedOptions .

document.querySelector('select').addEventListener('input', event => {
  // make selectedOptions iterable then reduce a new array object
  let selectData = Array.from(event.target.selectedOptions).reduce((data, opt) => {
    // parent optgroup label and option value are added to the reduce aggregator
    data.push([opt.parentElement.label.toLowerCase(), opt.value])
    return data
  }, [])
})

Теперь можно безопасно отправить форму или, как в случае с этой демонстрацией, указать Isotope, по чему фильтровать.

Завершение элемента роли статуса

Элемент только подсчитывает и объявляет количество фильтров на основе взаимодействия с флажком, но я посчитал хорошей идеей дополнительно поделиться количеством результатов и убедиться, что выбор элемента <select> также учитывается.

Выбор элемента <select> отражается в counter()

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

let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length

Результаты, отраженные в элементе role="status"

:checked предоставляет встроенный способ передачи количества выбранных фильтров в элемент роли статуса, но не обеспечивает видимость отфильтрованного количества результатов. JavaScript может отслеживать взаимодействие с флажками и после фильтрации сетки добавлять textContent , как это сделал элемент <select> .

document
  .querySelector('aside form')
  .addEventListener('input', e => {
    // isotope demo code
    let filterResults = IsotopeGrid.getFilteredItemElements().length
    document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})

В целом эту работу завершает анонс "2 фильтра, дающие 25 результатов".

Скриншот программы чтения с экрана MacOS, объявляющей результаты.

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

Заключение

Теперь, когда вы знаете, как я это сделал, как бы вы‽ 🙂

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

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

Здесь пока нечего смотреть!