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

Зачем разрабатывать свой Select, если есть тег <select>?

При создании селекта с помощью стандартного HTML-элемента <select>, мы ограничены в его изменении. Т.к. не всегда его можем настроить и стилизовать так, как нам это необходимо в том или ином конкретном проекте.

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

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

Вид селекта в обычном состоянии Вид селекта при выборе варианта из раскрывшегося dropdown меню

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

Подключение и использование компонента Select

Исходные коды разработанного компонента Select расположены на Github. Написан он с использованием CSS и чистого JavaScript. Зависимостей от jQuery и каких-либо других js-плагинов нет.

Для подключения компонента к странице достаточно к ней просто подключить CSS и JavaScript файл этого компонента или поместить эти коды в соответствующие свои файлы.

<!-- подключаем CSS селекта -->
<link rel="stylesheet" href="custom_select.css">

<!-- подключаем JS селекта -->
<script src="custom_select.js"></script>

Как использовать компонент Select?

Компонент Select можно использовать на странице 2 способами.

Первый способ подразумевает непосредственную вставку HTML кода селекта на страницу:

<div class="select" id="select-1">
  <div class="select__backdrop" data-select="backdrop"></div>
  
  <button type="button" class="select__trigger" data-select="trigger">
    Выберите из списка
  </button>
  
  <div class="select__dropdown">
    <ul class="select__items">
      <li class="select__item" data-select="item">Volkswagen</li>
      <li class="select__item select__item_selected" data-select="item">Ford</li>
      <li class="select__item" data-select="item">Toyota</li>
      <li class="select__item" data-select="item">Nissan</li>
    </ul>
  </div>
</div>

В содержимое элемента button необходимо поместить дефолтное значение. Это будет текущее значение. В select__items - варианты (элементы <li> с атрибутами class="select__item" data-select="item").

Если изначально какая-то опция должна быть активна, то к ней необходимо добавить класс select__item_selected. А также поместить её значение в кнопку.

После этого необходимо активировать эту структуру с помощью JavaScript как компонент Select.

Выполняется это посредством создания нового экземпляра объекта CustomSelect и передачей ему в качестве аргумента селектора:

const select1 = new CustomSelect('#select-1');

Второй способ предполагает использование Select без необходимости непосредственной вставки HTML структуры на страницу. Здесь достаточно лишь поместить контейнер (пустой элемент) в HTML документ.

<div id="select-1"></div>

Варианты и дефолтный текст (начальное значение) селекту необходимо передать при создании объекта в виде аргумента.

const select1 = new CustomSelect('#select-1', {
  // текст (значение) по умолчанию
  defaultValue: 'Ford',
 // опции
  data: ['Volkswagen', 'Ford', 'Toyota', 'Nissan'],
});

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

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

  • show – для отображения выпадающего списка с вариантами;
  • hide – для скрытия выпадающего списка;
  • toggle – для переключения состояния выпадающего списка;
  • destroy - для удаления обработчиков событий и элементов из DOM, связанных с этим селектом;
  • selectedItem(value) – для получения выбранного варианта и его установки.

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

1 способ (используя событие select.change):

document.querySelector('#select-1').addEventListener('select.change', (e) => {
  console.log(`Выбранное значение: ${ e.target.querySelector('.select__item_selected').textContent }`);
});

2 способ (через метод onSelect):

const select1 = new CustomSelect('#select-1', {
  defaultValue: 'Ford',
  data: ['Volkswagen', 'Ford', 'Toyota', 'Nissan'],
  onSelected(item) {
    console.log(`Выбранное значение: ${item.textContent}`);
  },
});

Как устроен компонент Select?

Компонент Select построен с использованием HTML, CSS и JavaScript.

HTML код компонента Select:

<div class="select">
  <div class="select__backdrop" data-select="backdrop"></div>
  <button type="button" class="select__trigger" data-select="trigger">Выберите из списка</button>
  <div class="select__dropdown">
    <ul class="select__items">
      <li class="select__item" data-select="item">Volkswagen</li>
      <li class="select__item select__item_selected" data-select="item">Ford</li>
      <li class="select__item" data-select="item">Toyota</li>
    </ul>
  </div>
</div>

Элемент с классом select определяет этот компонент. В нём находится вся HTML структура селекта. Тег <button> с атрибутами class="select__trigger" и data-select="trigger" предназначен для отображения выбранного значения и открытия при нажатии на него выдающего списка с опциями. Само выпадающее меню реализовано посредством элемента select__dropdown. Оно с помощью CSS настраивается так, чтобы оно было расположено под <button>. Список вариантов (.select__items) организован посредством маркированного списка. Выбранный элемент в нём отмечается посредством добавления к нему класса select__item_selected.

Кроме непосредственной вставки HTML кода на страницу, предоставим также возможность создавать его автоматически с помощью JavaScript. Таким образом, на страницу будет достаточно поместить пустой элемент и инициализировать его как Select. Как устроен JavaScript код приведём ниже.

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

Классы элементов будем использовать в CSS для добавления к ним стилей, а data атрибуты - в JavaScript.

CSS стили компонента Select можно посмотреть на GitHub.

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

.select {
  position: relative;
  ...
}

Элемент с классом select__trigger стилизуем в виде кнопки (текущий вариант в ней будем выводить как её содержимое).

.select__trigger {
  display: flex;
  background-color: #fff;
  border: 1px solid #ccc;
  border-radius: 0.3125rem;
  cursor: pointer;
  align-items: center;
  width: 100%;
  font-size: 1rem;
  padding: 0.375rem 0.75rem;
  line-height: 1.4;
  user-select: none;
  font-size: 1rem;
  justify-content: space-between;
  font-style: italic;
}

Иконку к кнопке добавим через псевдоэлемент ::after:

.select__trigger::after {
  content: '';
  width: 0.75rem;
  height: 0.75rem;
  background-size: cover;
  background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" height="100" width="100"%3E%3Cpath d="M97.625 25.3l-4.813-4.89c-1.668-1.606-3.616-2.41-5.84-2.41-2.27 0-4.194.804-5.777 2.41L50 52.087 18.806 20.412C17.223 18.805 15.298 18 13.03 18c-2.225 0-4.172.804-5.84 2.41l-4.75 4.89C.813 26.95 0 28.927 0 31.23c0 2.346.814 4.301 2.439 5.865l41.784 42.428C45.764 81.174 47.689 82 50 82c2.268 0 4.215-.826 5.84-2.476l41.784-42.428c1.584-1.608 2.376-3.563 2.376-5.865 0-2.26-.792-4.236-2.375-5.932z"/%3E%3C/svg%3E');
}

По умолчанию dropdown меню не будет показываться. Включение его отображения будем осуществлять посредством добавления к нему класса select_show:

.select_show .select__dropdown {
  display: block;
}

При этом при показе dropdown меню иконку будем поворачивать на 180 градусов посредством CSS трансформации:

.select_show .select__trigger::after {
  transform: rotate(180deg);
}

CSS код для стилизации dropdown меню:

.select__dropdown {
  display: none;
  position: absolute;
  top: 2.5rem;
  left: 0;
  right: 0;
  border: 1px solid #ccc;
  max-height: 10rem;
  overflow-y: auto;
  border-radius: 0.3125rem;
}

.select__items {
  margin: 0;
  padding: 0;
  list-style: none;
}

.select__item {
  padding: 0.375rem 0.75rem;
}

Стилизация при наведении на пункт меню:

.select__item:hover {
  background-color: #f5f5f5;
  cursor: pointer;
  transition: 0.2s background-color ease-in-out;
}

Стили для элемента с классом select__backdrop:

.select__backdrop {
  position: fixed;
  z-index: -1;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  display: none;
  background-color: transparent;
}

JavaScript код компонента доступен на GitHub. Его можно открыть, используя эту ссылку.

Код написан с использованием класса:

class CustomSelect {
  ...
}

Приватные свойства и методы выделим через добавления к ним (перед именем) нижнего подчеркивания.

class CustomSelect {
  constructor(selector, config) {
    // приватное свойство
    this._$main = document.querySelector(selector);
    ...
  }
  ...
  // приватный метод
  _isShow() { ... }
  ...
}

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

Структура JavaScript кода:

class CustomSelect {
  constructor(selector, config) {
    ...
  }
  ...
  // возвращает true, если у .select есть класс select_show; в противном случае false
  _isShow() { ... }
  // предназначен для изменения выбранного элемента; элемент на который мы нажали передаётся в этот метод посредством аргумента
  _changeItem(item) { ... }
  // метод, код которого будет выполняться при возникновении события click на Select
  _eventHandler(e) { ... }
  // используется для установке обработчика события click, а также для создания кастомного события 'select.change'
  _addEventListener() { ... }
  // метод для динамического создания элемента
  _render() { ... }

  /* публичные методы */
  // для показа dropdown меню
  show() { ... }
  // для скрытия dropdown меню
  hide() { ... }
  // для переключения видимости dropdown меню
  toggle() { ... }
  // для удаления слушателей события click селекта и DOM элементов селекта со страницы
  destroy() { ... }
  // может использоваться для получения выбранного значения или установке селекту другого значения как по его порядковому её номеру, так и по переданному тексту
  selectedItem(value) { ... }
}