DevGang
Авторизоваться

Создание настраиваемого списка выбора с помощью CSS

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

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

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

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

Далее в этой статье мы также рассмотрим создание пользовательского виджета select с нуля наиболее эффективным способом с использованием JavaScript.

В этом уроке мы создадим два виджета выбора. Вы можете посмотреть готовые демо-версии на CodePen: одна создана с использованием только CSS, а другая - с использованием CSS и JavaScript.

Понимание принципов работы элемента select

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

Используя следующую разметку HTML, мы можем создать выпадающий список с семью вариантами выбора:

<div class="custom-select">
  <select>
    <option value="">Open this select menu</option>
    <option value="">GitHub</option>
    <option value="">Instagram</option>
    <option value="">Facebook</option>
    <option value="">LinkedIn</option>
    <option value="">Twitter</option>
    <option value="">Reddit</option>
  </select>
</div>

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

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

Если вы используете Chrome, то в настройках браузера можно включить опцию Show user agent shadow DOM, чтобы увидеть теневой DOM:

Одним из преимуществ использования теневого DOM является предотвращение конфликтов между стилями компонента и стилями других элементов в реальном DOM. В результате пользовательский интерфейс становится более предсказуемым и надежным.

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

Настройка выпадающего списка только с помощью CSS

Давайте вернемся к разметке выпадающего списка, о которой мы говорили ранее:

<div class="custom-select">
  <select>
    <option value=""> ... </option>
  </select>
</div>

Мы можем добавить следующий CSS для обеспечения некоторых начальных стилей:

.custom-select {
  min-width: 350px;
}

select {
  appearance: none;
  /* safari */
  -webkit-appearance: none;
  /* other styles for aesthetics */
  width: 100%;
  font-size: 1.15rem;
  padding: 0.675em 6em 0.675em 1em;
  background-color: #fff;
  border: 1px solid #caced1;
  border-radius: 0.25rem;
  color: #000;
  cursor: pointer;
}

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

Из приведенного ниже результата видно, что установка свойства appearance в значение none также удаляет родную выпадающую стрелку:

Давайте создадим пользовательскую стрелку для нашего раскрывающегося списка <select>!

Добавление пользовательской стрелки к собственному раскрывающемуся списку select

Используя псевдоэлементы CSS, мы можем создать пользовательскую стрелку без необходимости добавления дополнительного элемента HTML. Для этого мы применим к элементу-контейнеру свойство position: relative:

.custom-select {
  /* ... */
  position: relative;
}

Затем мы расположим наши CSS-псевдоэлементы, такие как ::before и ::after, непосредственно внутри контейнера:

.custom-select::before,
.custom-select::after {
  --size: 0.3rem;
  position: absolute;
  content: "";
  right: 1rem;
  pointer-events: none;
}

.custom-select::before {
  border-left: var(--size) solid transparent;
  border-right: var(--size) solid transparent;
  border-bottom: var(--size) solid black;
  top: 40%;
}

.custom-select::after {
  border-left: var(--size) solid transparent;
  border-right: var(--size) solid transparent;
  border-top: var(--size) solid black;
  top: 55%;
}

В коде мы использовали "трюк с границами" для создания стрелок вверх и вниз. Используя таким образом границы CSS, мы создали форму стрелок, которая соответствует желаемому виду.

Ниже показано поведение без объявления CSS pointer-events: none;:

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

Результат можно посмотреть на CodePen.

Особенности реализации на основе CSS

При настройке выпадающего списка <select> с помощью этого метода следует учитывать несколько особенностей.

Для обеспечения единообразия дизайна в браузерах мы используем стратегический подход, стилизуя "родной" выпадающий список <select> под стандартный вид браузера. Это позволяет сохранить привычный и единообразный вид выпадающего списка, обеспечивая пользователям единообразное восприятие во всех браузерах.

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

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

select {
  /* ... */
  background-color: #000;
  color: #fff;
}

.custom-select::before {
  /* ... */
  border-bottom: var(--size) solid #fff;
}

.custom-select::after {
  /* ... */
  border-top: var(--size) solid #fff;
}

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

В следующем разделе мы рассмотрим, как создать полностью пользовательский раскрывающийся <select> с нуля.

Пользовательский список select с нуля с помощью CSS и JavaScript

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

Посмотрите на разметку, приведенную ниже:

<div class="custom-select">
  <button class="select-button">
    <span class="selected-value">Open this select menu</span>
    <span class="arrow"></span>
  </button>
  <ul class="select-dropdown">
    <li>
      <input type="radio" id="github" name="social-account" />
      <label for="github">GitHub</label>
    </li>
    <li>
      <input type="radio" id="instagram" name="social-account" />
      <label for="instagram">Instagram</label>
    </li>
    <!-- ... -->
  </ul>
</div>

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

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

Результат:

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

Добавление возможностей доступа

На следующем этапе мы добавим соответствующие атрибуты ARIA, чтобы сделать выпадающий список <select> более доступным для людей с ограниченными возможностями.

Ниже приведены необходимые атрибуты для нашего виджета:

<div class="custom-select">
  <button
    class="select-button"
    role="combobox"
    aria-labelledby="select button"
    aria-haspopup="listbox"
    aria-expanded="false"
    aria-controls="select-dropdown"
  >
    <!-- ... -->
  </button>
  <ul ... role="listbox" id="select-dropdown">
    <li role="option">...</li>
    <li role="option">...</li>
  </ul>
</div>

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

  • role="combobox" идентифицирует кнопку как элемент, управляющий полем списка
  • aria-labelledby описывает назначение кнопки
  • aria-haspopup информирует программу чтения с экрана пользователя о наличии интерактивного всплывающего элемента и описывает его тип
  • aria-controls связывает управляющий элемент с расширенным виджетом. В данном случае мы присвоили ID расширенного виджета атрибуту aria-controls управляющего элемента
  • aria-expanded переключается между значениями true и false для указания состояния выпадающего списка - другими словами, является ли выпадающий список <select> в данный момент скрытым или видимым. Позже мы будем использовать JavaScript для динамического обновления значения, чтобы отразить текущее состояние выпадающего списка.

Далее, на элементе ul:

  • role="listbox" идентифицирует ul как список, из которого пользователь может выбрать элемент
  • role="option" представляет собой индивидуальный выбираемый вариант для каждого элемента списка

Теперь перейдем к стилизации нашего списка <select>.

Стилизация выпадающего списка select

Начнем со стилизации начального вида - т.е. кнопки выбора и ее содержимого - с помощью следующего CSS:

.custom-select {
  position: relative;
  width: 400px;
  max-width: 100%;
  font-size: 1.15rem;
  color: #000;
  margin-top: 3rem;
}

.select-button {
  width: 100%;
  font-size: 1.15rem;
  background-color: #fff;
  padding: 0.675em 1em;
  border: 1px solid #caced1;
  border-radius: 0.25rem;
  cursor: pointer;

  display: flex;
  justify-content: space-between;
  align-items: center;
}

.selected-value {
  text-align: left;
}

.arrow {
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid #000;
  transition: transform ease-in-out 0.3s;
}

В приведенном выше коде мы применили различные CSS-стили для улучшения общей эстетики раскрывающегося <select>.

Важной деталью здесь является параметр position: relative, который мы применили к содержащему элементу. Это позволит нам разместить выпадающий элемент непосредственно под кнопкой. Когда пользователь откроет выпадающий список, он будет перекрывать другое содержимое страницы.

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

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

.select-dropdown {
  position: absolute;
  list-style: none;
  width: 100%;
  box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
  background-color: #fff;
  border: 1px solid #caced1;
  border-radius: 4px;
  padding: 10px;
  margin-top: 10px;
  max-height: 200px;
  overflow-y: auto;
  transition: 0.5s ease;
}

.select-dropdown:focus-within {
  box-shadow: 0 10px 25px rgba(94, 108, 233, 0.6);
}

.select-dropdown li {
  position: relative;
  cursor: pointer;
  display: flex;
  gap: 1rem;
  align-items: center;
}

.select-dropdown li label {
  width: 100%;
  padding: 8px 10px;
  cursor: pointer;
}

.select-dropdown::-webkit-scrollbar {
  width: 7px;
}
.select-dropdown::-webkit-scrollbar-track {
  background: #f1f1f1;
  border-radius: 25px;
}

.select-dropdown::-webkit-scrollbar-thumb {
  background: #ccc;
  border-radius: 25px;
}

Мы использовали несколько различных CSS-свойств, но давайте поговорим о псевдоклассе :focus-within, который мы применили к выпадающему списку.

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

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

Добавление состояний hover, checked и focus

Для обеспечения доступности и плавности пользовательского интерфейса мы добавим стили для состояний focus, hover и active, чтобы создать визуальный эффект при взаимодействии со списком <select>:

.select-dropdown li:hover,
.select-dropdown input:checked ~ label {
  background-color: #f2f2f2;
}

.select-dropdown input:focus ~ label {
  background-color: #dfdfdf;
}

GIF ниже демонстрирует, как ведет себя виджет select с только что добавленным пользовательским стилем:

Скрытие ввода

Теперь, когда мы видим, как работает ввод данных, мы можем скрыть его с помощью следующих правил CSS:

.select-dropdown input[type="radio"] {
  position: absolute;
  left: 0;
  opacity: 0;
}

Реализация кода JavaScript

Мы будем использовать JavaScript для переключения выпадающего списка и выбора варианта из списка. Для начала внесем изменения в CSS:

.select-dropdown {
  /* ... */

  transform: scaleY(0);
  opacity: 0;
  visibility: hidden;
}

/* .... */

/* interactivity */
.custom-select.active .arrow {
  transform: rotate(180deg);
}

.custom-select.active .select-dropdown {
  opacity: 1;
  visibility: visible;
  transform: scaleY(1);
}

Мы скрыли выпадающий список по умолчанию и применили стилевые правила для преобразования стрелочного индикатора и отображения выпадающего списка при применении класса .active к элементу контейнера. Мы добавим класс .active с помощью JavaScript:

const customSelect = document.querySelector(".custom-select");
const selectBtn = document.querySelector(".select-button");

// add a click event to select button
selectBtn.addEventListener("click", () => {
  // add/remove active class on the container element
  customSelect.classList.toggle("active");
  // update the aria-expanded attribute based on the current state
  selectBtn.setAttribute(
    "aria-expanded",
    selectBtn.getAttribute("aria-expanded") === "true" ? "false" : "true"
  );
});

Приведенный выше код JavaScript начинает с получения ссылки на кнопку select и элемент контейнера. Затем он прослушивает событие щелчка на кнопку и динамически переключает класс .active на элементе контейнера.

Он также динамически обновляет атрибут aria-expanded на самой кнопке в зависимости от текущего состояния.

Результат:

Отображение выбранного параметра

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

const selectedValue = document.querySelector(".selected-value");
const optionsList = document.querySelectorAll(".select-dropdown li");

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

optionsList.forEach((option) => {
  function handler(e) {
    // Click Events
    if (e.type === "click" && e.clientX !== 0 && e.clientY !== 0) {
      selectedValue.textContent = this.children[1].textContent;
      customSelect.classList.remove("active");
    }
    // Key Events
    if (e.key === "Enter") {
      selectedValue.textContent = this.textContent;
      customSelect.classList.remove("active");
    }
  }

  option.addEventListener("keyup", handler);
  option.addEventListener("click", handler);
});

Добавление вторичной информации в опции select

Давайте добавим иконки к опциям с помощью Boxicons. Начнем с добавления CDN Boxicons в элемент <head>:

<head>
  <!-- ... -->
  <link
    href="https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css"
    rel="stylesheet"
  />
</head>

Затем добавьте необходимые значки рядом с текстом метки:

<ul class="select-dropdown" role="listbox" id="select-dropdown">
  <li role="option">
    <!-- ... -->
    <label for="github"><i class="bx bxl-github"></i>GitHub</label>
  </li>
  <li role="option">
    <!-- ... -->
    <label for="instagram"><i class="bx bxl-instagram"></i>Instagram</label
    >
  </li>
  <!-- ... -->
</ul>

После этого примените следующий CSS для улучшения интервалов и выравнивания:

.select-dropdown li label {
  /* ... */
  display: flex;
  gap: 1rem;
  align-items: center;
}

Окончательный результат можно посмотреть на сайте CodePen.

Заключение

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

Первый подход использовал CSS для настройки собственного элемента <select>. Мы добились пользовательского стиля, тщательно стилизовав первоначальный вид, что позволило создать более последовательный и визуально привлекательный компонент.

Во втором случае мы создали пользовательский выпадающий список с нуля с помощью CSS и JavaScript. Мы создали полностью пользовательский выпадающий список выбора с использованием семантических элементов для обеспечения доступности и интерактивности клавиатуры.

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

Источник:

#CSS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу