Как создать свой Select на CSS и JavaScript?

Александр Мальцев
Александр Мальцев
10K
17
Как создать свой Select на CSS и JavaScript?
Содержание:
  1. Зачем разрабатывать свой Select, если есть тег <select>
  2. Подключение и использование компонента Select
  3. Как использовать компонент CustomSelect
  4. Как устроен компонент Select
  5. Комментарии

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

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

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

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

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

Вид селекта, спроектированного на чистом CSS и JavaScript, в закрытом состоянии Вид селекта, спроектированного на чистом CSS и JavaScript, в открытом состоянии

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

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

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

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


<div class="select" id="select-1">
  
  <button type="button" class="select__toggle" name="car" value="" data-select="toggle" data-index="-1">Выберите из списка</button>
  
  <div class="select__dropdown">
    <ul class="select__options">
      <li class="select__option" data-select="option" data-value="volkswagen" data-index="0">Volkswagen</li>
      <li class="select__option" data-select="option" data-value="ford" data-index="1">Ford</li>
      <li class="select__option" data-select="option" data-value="toyota" data-index="2">Toyota</li>
      <li class="select__option" data-select="option" data-value="nissan" data-index="3">Nissan</li>
    </ul>
  </div>
</div>

Элемент <button> представляет собой кнопку, предназначенную для открытия выпадающего списка. Её действие в JavaScript коде определяется с помощью атрибута data-select="toggle".

Также кнопка содержит пару «имя=значение» (где значение – это значение выбранной опции). Имя устанавливается атрибутом name, а значение – value. Поэтому если CustomSelect поместить в форму, то значение выбранной опции вместе с другими данными будут отправлены на сервер.

data-index содержит индекс выбранной опции. Если по умолчанию не должна быть установлена какая-то опция, то атрибуту value следует задать пустую строку, а data-index – значение -1.

В select__options находятся опции: <li> с классом select__option. data-select="option" определяет действие аналогично с кнопкой, data-value – значение опции, а data-index – её индекс (порядковый номер).

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


<div class="select" id="select-1">
  <button type="button" class="select__toggle" name="car" value="ford" data-select="toggle" data-index="1">Выберите из списка</button>
  <div class="select__dropdown">
    <ul class="select__options">
      <li class="select__option" data-select="option" data-value="volkswagen" data-index="0">Volkswagen</li>
      <li class="select__option select__option_selected" data-select="option" data-value="ford" data-index="1">Ford</li>
      <li class="select__option" data-select="option" data-value="toyota" data-index="2">Toyota</li>
      <li class="select__option" data-select="option" data-value="nissan" data-index="3">Nissan</li>
    </ul>
  </div>
</div>

После создания необходимой HTML-структуры необходимо активировать корневой элемент (.select) как CustomSelect с помощью JavaScript.

Выполняется это следующим образом:

// #select-1 - селектор для выбора элемента, который необходимо инициализировать как CustomSelect
const select1 = new CustomSelect('#select-1');

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

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

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

const select2 = new CustomSelect('#select-2', {
  name: 'car', // значение атрибута name у кнопки
  targetValue: 'ford', // значение по умолчанию
  options: [['volkswagen', 'Volkswagen'], ['ford', 'Ford'], ['toyota', 'Toyota'], ['nissan', 'Nissan']], // опции
});

Значение options - это массив массивов. Первый элемент массива – это значение опции, а второй – её текстовое представление.

Если не нужно чтобы CustomSelect имел какое-то значение по умолчанию, то установите ключу targetValue пустую строку или вообще его не указывайте:

const select2 = new CustomSelect('#select-2', {
  name: 'car', // значение атрибута name у кнопки
  options: [['volkswagen', 'Volkswagen'], ['ford', 'Ford'], ['toyota', 'Toyota'], ['nissan', 'Nissan']], // опции
});

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

  • value – позволяет как получить выбранную опцию, так и установить её;
  • selectedIndex – индекс выбранного элемента (нумерация начинается с 0);
  • show() – показывает выпадающий список с опциями;
  • hide() – скрывает dropdown меню;
  • toggle() – переключает видимость выпадающего меню;
  • dispose() - удаляет обработчики событий, связанных с этим селектом.

Использование свойства value:

// установим в качестве выбранной опции элемент со значением toyota
select2.value = 'toyota';
// получим значение выбранной опции
console.log( select2.value ); // toyota

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

// сбросим выбранную опцию
select2.value = '';

Использование свойства selectedIndex:

// установим в качестве выбранной опции элемент с индексом 2
select2.selectedIndex = 2;
// получим индекс выбранной опции
console.log( select2.selectedIndex );

Если ни один из элементов не выбран, то selectedIndex возвращает -1:

select2.value = '';
// получим индекс выбранного элемента
console.log( select2.selectedIndex ); // -1

Сбросить выбранный элемент можно не только посредством value, но также, если установить selectedIndex число -1 или индекс элемента, которого нет:

select2.selectedIndex = -1;

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

document.querySelector('#select-2').addEventListener('select.change', (e) => {
  const btn = e.target.querySelector('.select__toggle');
  // выбранное значение
  console.log(`Выбранное значение: ${btn.value}`);
   // индекс выбранной опции
  console.log(`Индекс выбранной опции: ${btn.dataset.index}`);
  // выбранный текст опции
  const selected = e.target.querySelector('.select__option_selected');
  const text = selected ? selected.textContent : '';
  console.log(`Выбранный текст опции: ${text}`);
});

Кроме как использовать событие, это действие также можно выполнить с помощью метода onSelected при создании экземпляра объекта CustomSelect:

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

new CustomSelect('#select-2', {
  name: 'car',
  targetValue: 'ford',
  data: [['volkswagen', 'Volkswagen'], ['ford', 'Ford'], ['toyota', 'Toyota'], ['nissan', 'Nissan']],
  onSelected(select, option) {
    // выбранное значение
    console.log(`Выбранное значение: ${select.value}`);
    // индекс выбранной опции
    console.log(`Индекс выбранной опции: ${select.selectedIndex}`);
    // выбранный текст опции
    const text = option ? option.textContent : '';
    console.log(`Выбранный текст опции: ${text}`);
  }
});

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

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

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

<div class="select">
  <button type="button" class="select__toggle" name="car" value="" data-select="toggle" data-index="-1">Выберите из списка</button>
  <div class="select__dropdown">
    <ul class="elect__options">
      <li class="elect__option" data-select="option" data-value="volkswagen" data-index="0">Volkswagen</li>
      <li class="elect__option elect__option_selected" data-select="option" data-value="ford" data-index="1">Ford</li>
      <li class="elect__option" data-select="option" data-value="toyota" data-index="2">Toyota</li>
    </ul>
  </div>
</div>

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

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

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

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

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

.select {
  position: relative;
  ...
}

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

.select__toggle {
  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__toggle::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__toggle::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__options {
  margin: 0;
  padding: 0;
  list-style: none;
}
.select__option {
  padding: 0.375rem 0.75rem;
}

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

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

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

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


class CustomSelect {
  ...
}

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

class CustomSelect {
  constructor(target, params) {
    this._elRoot = typeof target === 'string' ? document.querySelector(target) : target;
    ...
  }
  ...
  _onClick(e) { ... }
  ...
}

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

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

class CustomSelect {
  constructor(target, params) { }
  // обработчик события click
  _onClick(e) { }
  // сбрасывает состояние, генерирует событие 'select.change'
  _reset() { }
  // обновляет значения атрибутов в зависимости от выбранной опции, генерирует событие 'select.change'
  _update(option) { }
  // при изменении выбранной опции
  _changeValue(option) { }

  // включает отображение выпадающего списка
  show() { }
  // скрывает список с опциями
  hide() { }
  // переключает список с опциями
  toggle() { }
  // удаления слушателей события click селекта
  dispose() { }
  // возвращает значение выбранной опции
  get value() { }
  // позволяет установить опцию по значению
  set value(value) { }
  // возвращает индекс выбранной опции
  get selectedIndex() { }
  // позволяет выбрать опцию по её индексу
  set selectedIndex(index) { }
}
// функция для генерации HTML-кода селекта в зависимости от переданных аргументов
CustomSelect.template = params => { };
// для закрытия открытого селекта при клике вне его
document.addEventListener('click', (e) => {
  if (!e.target.closest('.select')) {
    document.querySelectorAll(SELECTOR_ACTIVE).forEach(select => {
      select.classList.remove(CLASS_NAME_ACTIVE);
    });
  }
});

Комментарии:

  1. Александр
    Александр
    14.10.2021, 12:46
    Александр, добрый день. Имеется ли возможность задавать разные значения переменной
    selectedContent
    для разных селектов. По умолчанию стоит 'Выберите из списка';
    1. Daniel
      Daniel
      29.09.2021, 10:04
      Здраствуйте Александр. Большое спасибо за разработанный плагин CustomSelect, отличная работа. сэкономлено кучу времени. У меня возник один вопрос: на базе вашего плагина пишу калькулятор и столкнулся с небольшой проблемой, а именно: создаю функцию Reset для сброса всех значений «name», «value» в дефолтное состояние но при клике на кнопке reset не получаю никакого результата.
      Вот пример кода:
      function resetBtn() {
        let btnReset = document.queryselectorAll("select-reset");
        btnReset.value = "" ;
        btnRest. name = "" ;
      };
      <button type="reset" onClick="resetBtn()">Reset</button>
      Буду очень благодарен за помощь. Спасибо.
      1. Александр Мальцев
        Александр Мальцев
        29.09.2021, 14:20
        Добрый день! Наверно так:
        function resetBtn() {
          let btnReset = document.querySelectorAll('.select-reset');
          btnReset.forEach((element) => {
            element.value = '';
            element.name = '';
          });
        }
        Не знаю, что такое select-reset? Класс?
        После того как выбрали элементы с помощью querySelectorAll, их нужно перебрать, например, с помощью forEach. Так напрямую нельзя.
        1. Daniel
          Daniel
          29.09.2021, 14:46
          Большое спасибо за столь быстрый ответ. да, select-reset это я добавил некий клас, для сброса значений и в калькуляторе я использую несколько CustomSelect и радио-кнопки checkbox. ok, попробую с forEach. Ещё раз спасибо. Хорошего дня
      2. Daria
        Daria
        20.08.2021, 18:19
        Здравствуйте, Александр, спасибо вам за проделанную работу, ваш селект прекрасен))

        у меня возникла проблема,
        Мне нужно два селекта, они расположены рядом в форме, добавляю их Первым способом
        все работает хорошо, кроме закрытия по клику на второй селект.

        1) Кликаем на первый селект, ничего, не выбрано, кликаем, на второй селект, первый остается в открытом состоянии, но не активен при ховере, если кликнуть «оживает», получается два открытых одновременно селекта.

        При этом в обратном порядке работает правильно
        2) Кликаем второй селект, кликаем на первый второй закрыватся при клике на первый.

        1. Александр Мальцев
          Александр Мальцев
          21.08.2021, 12:04
          Привет! Спасибо за отзыв. Переписал код компонента, а также исправил этот баг. Также изменил название классов и атрибутов, поэтому обратите на это внимание при обновлении кода.
        2. Кирилл
          Кирилл
          18.08.2021, 18:13
          Верно ли понимаю, что мы можем управлять выбором значения по индексу или названию(value), так:
          select1.selectedItem(6); // (где 6 — это 7 элемент списка).
          Но у меня к сожалению, так не сработало :(

          P.S. спасибо большое за реализацию.
          1. Александр Мальцев
            Александр Мальцев
            19.08.2021, 03:34
            Нужно передавать в формате объекта:
            // по индексу
            select1.selectedItem({index: 0})
            // по значению
            select1.selectedItem({value: 'Nissan'})
            1. Кирилл
              Кирилл
              19.08.2021, 09:37
              Благодарю!
          2. Konstantin
            Konstantin
            03.06.2021, 13:35
            Может быть это у меня что то с ip, но ни одна ссылка на материалы из этого поста не открывается. Выдает 404 ошибку. в том числе на GIT!!!
            1. Александр Мальцев
              Александр Мальцев
              03.06.2021, 13:50
              Поправил ссылки.
            2. Александр
              Александр
              24.11.2020, 17:07
              Александр, здравствуйте! Спасибо, за отличный вариант и достойную альтернативу дефолтному селекту. Пытаюсь это решение использовать в сортировке mFilter2, не получается всё сделать по уму да и вообще мало что получается честно говоря, делаю так:

              <div class="select" id="mse2_sort">
                <div class="select__backdrop" data-select="backdrop"></div>
                
               <button type="button" class="select__trigger" data-select="trigger" value="asc" data-sort="resource|pagetitle" data-icon="fa-list">
                  По умолчанию
               </button>
              
              <div class="select__dropdown">
                  <ul class="select__items">
                    <li class="select__item" data-select="item"  value="asc" data-sort="extendresource|date"><i class="fa fa-play"></i>Дата</li>
                    <li class="select__item" data-select="item" value="desc" data-sort="extendresource|size"><i class="fa fa-table"></i>Размер</li>
                  </ul>
                </div>
              </div>
              <script>
               const select1 = new CustomSelect('#mse2_sort'); 
              </script>
              как я понимаю, так работать не будет, так как в файле default.js в параметрах скрипта mFilter2, завязано всё на теге «a»
              sort_link: '#mse2_sort a'
              если изменить на тег «li», то сортировка начинает работать, но не сохраняет выбранное значение при перезагрузке страницы. Александр, если возможно расскажите пожалуйста как нужно и лучше сделать, чтобы сортировка работала и сохраняла выбранное значение при перезагрузки страницы?
              1. Александр Мальцев
                Александр Мальцев
                26.11.2020, 16:53
                Привет! Спасибо за отзыв.
                Чтобы в mFilter2 изменить sort_link можно использовать параметр &filterOptions:
                [[!mFilter2? 
                  ...
                  &filterOptions=`{"sort_link": "#mse2_sort li"}`
                ]]
                Установить выбранную сортировку в CustomSelect после перезагрузки страницы можно посредством добавления следующего скрипта на страницу:
                <script>
                const queryString = window.location.search;
                const urlParams = new URLSearchParams(queryString);
                const sort = urlParams.get('sort');
                let value = '';
                if (sort) {
                  const delim = sort.indexOf(':');
                  const res = delim === -1 ? sort : sort.substring(0, delim);
                  const $li = document.querySelector(`#mse2_sort li[data-sort="${res}"]`);
                  if ($li) {
                    value = $li.textContent;
                  }
                }
                const select1 = new CustomSelect('#mse2_sort');
                if (value) {
                  select1.selectedItem({value: value})
                } 
                </script>
                1. Александр
                  Александр
                  26.11.2020, 20:20
                  Александр, огромное спасибо за решение, в очередной раз выручили.
              2. Владимир
                Владимир
                07.11.2020, 23:40
                Александр, еще момент.

                1. Александр Мальцев
                  Александр Мальцев
                  08.11.2020, 07:28
                  Спасибо! Этот момент тоже доработал и обновил код js-файла на Github.
                2. Владимир
                  Владимир
                  07.11.2020, 15:03
                  Еще момент.
                  Если создание нового экземпляра объекта CustomSelect происходит таким образом
                  const select1 = new CustomSelect(".select");
                  то в консоли получаю следующую ошибку
                  1. Александр Мальцев
                    Александр Мальцев
                    07.11.2020, 15:47
                    Спасибо! Поправил код скрипта на Github.
                  2. Владимир
                    Владимир
                    07.11.2020, 14:58
                    Добрый день!

                    На странице категории есть сортировка, сделанная через стандартный 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">
                          <?php foreach ($sorts as $sorts) { ?>
                          <?php if ($sorts['value'] == $sort . '-' . $order) { ?>
                          <li class="select__item select__item_selected" data-select="item" data-url="<?php echo $sorts['href']; ?>"><?php echo $sorts['text']; ?></li>
                          <?php } else { ?>
                          <li class="select__item" data-select="item" data-url="<?php echo $sorts['href']; ?>"><?php echo $sorts['text']; ?></li>
                          <?php } ?>
                          <?php } ?>
                        </ul>
                      </div>
                    </div>
                    <script>
                    const select1 = new CustomSelect(".select", { });
                    
                    document.querySelector(".select").addEventListener("select.change", (e) => {
                      location = document.querySelector(".select__item_selected").getAttribute("data-url"); 
                    });
                    </script>
                    Не получается подставить выбранное значение в button.
                    Подскажите, пожалуйста, как это можно сделать?
                    1. Александр Мальцев
                      Александр Мальцев
                      07.11.2020, 15:36
                      Здравствуйте!
                      Установите выбранное значение в button с помощью JavaScript:
                      document.querySelector('.select__trigger').textContent = document.querySelector('.select__item_selected').textContent;
                      
                      1. Владимир
                        Владимир
                        07.11.2020, 16:01
                        Оказалось все так просто.
                        Спасибо, Александр!
                    Войдите, пожалуйста, в аккаунт, чтобы оставить комментарий.