Разумеется, хороший специалист может стилизовать что угодно, однако всё упирается в сложность этого действия.
Для стилизации радиокнопок и чекбоксов потребуется совсем немного времени — идея стилизации label со скрытыми input не нова, и давно и повсеместно используется.
Сегодня я хочу рассказать Вам о том, как можно относительно просто стилизовать выпадающие списки, с минимальным кодом JavaScript (от 0 до 26 строк) и минимальной дополнительной разметкой HTML.
На днях у меня, как это часто и бывает, возникла необходимость стилизовать выпадающий список. Однако именно выпадающим списком он становился только в мобильной версии, до этого же он вёл себя как самая обычная группа радиокнопок.
И вот, когда я уже почти закончил писать очередную жуткую конструкцию, призванную заменить несчастный Select, у меня возник вопрос:
А зачем?
Обычно этот вопрос ведёт к умудрённой прокрастинации, переосмыслению жизни и т.д., но в этот раз что-то пошло не так, и я понял, что вопрос и правда был хорошим, а главное — полезным.
Небольшой ликбез:
Итак, для тех, кто в танке, а также тех, кому выпадающие списки доставили столько боли, что они решили забыть о них, как о страшном сне, я немножко (правда) напомню о том, что это такое.
Выпадающие списки состоят из 2 основных элементов:
- Select — контейнер для всего списка
- Option — элемент списка
Иногда используется ещё и OptGroup (группа элементов списка), но его реализация пока что остаётся на JavaScript'е.
Основной функционал выпадающего списка — отправка информации о выбранном(ых) пункте(ах) на сервер.
Для полноты картины, вот список атрибутов элементов (чтобы можно было оценить, сколько из них будут реализованы):
- Select — disabled, form, multiple, name, required, size
- Option — disabled, label, selected, value
На этом, пожалуй, закончим с описанием, и перейдём к реализации.
Уже было написано немало слов, поэтому — ближе к коду:
<form>
<div class="content">
<h3 class="title">Описание 0 пункта</h3>
<input id="selectName0" type="radio" value="0" name="selectName" />
<label for="selectName0">Описание 0 пункта</label>
<input id="selectName1" type="radio" value="1" name="selectName" />
<label for="selectName1">Описание 1 пункта</label>
<input id="selectName2" type="radio" value="2" name="selectName" />
<label for="selectName2">Описание 2 пункта</label>
</div>
</form>
Итак, чем же хорош этот код? (всё, без чего можно обойтись при объяснении, убрано специально)
Давайте отметим, какие пункты из функционала Select и Option автоматически стали возможны, за счёт использования input + label:
- Select — form, name, required
- Option — disabled, label, selected, value
И вот всё, что остаётся реализовать:
- Select — disabled, multiple, size
- Option — полностью реализовано
Всего лишь добавлением связок input + label, мы сократили список необходимого функционала с 10 пунктов до 3. Неплохо, но ведь это не конец, верно?
Чтобы реализовать пункт Select.multiple (множественный выбор) — достаточно сделать так:
<form>
<div class="content">
<input id="selectName0" type="checkbox" value="0" name="selectName" />
<label for="selectName0">Описание 0 пункта</label>
<input id="selectName1" type="checkbox" value="1" name="selectName" />
<label for="selectName1">Описание 1 пункта</label>
<input id="selectName2" type="checkbox" value="2" name="selectName" />
<label for="selectName2">Описание 2 пункта</label>
</div>
</form>
Мы всего-навсего меняем тип input'ов с Radio на Checkbox, и получаем практически полный аналог multiple.
Разница в том, что для множественного выбора не нужно зажимать ctrl (ну а если кто-то хочет полностью имитировать функционал — JavaScript в помощь).
Что осталось?
- Select — disabled, size
- Option — полностью реализовано
Ну что ж, и Select.disabled и Select.size предельно просто реализуются с помощью CSS:
- Select.size — какой зададите размер, такой и будет. Необходимо будет просто добавить контейнер.
- Select.disabled — для контейнера нужно добавить pointer-events: none, чтобы отменить реакцию на любые действия пользователя (наведение, клики и т.д.), ну и можно сделать его чуть прозрачным.
Итак, основной функционал есть. Чего не хватает?
Нужно также добавить автозаполнение заголовка для выпадающего меню с единственным выбором, а также возникнет проблема при сбросе формы (кнопка Reset), т.к. заголовок не сбросится. Но и это — вполне решаемо (с помощью JavaScript).
А теперь — примеры:
Немного реализации (выпадающее меню с единичным выбором):
И ещё немного (не выпадающее меню с множественным выбором):
Бонус: не знаю, зачем я это сделал, но… выпадающее меню с единичным выбором и работающим на CSS сбросом заголовка:
Зачем это нужно?
В связи с тем, что судя по комментариям, не все понимают, для чего будет полезен данный код, считаю нужным дать некоторые разъяснения.
При стилизации выпадающих списков вида:
<select>
<option>Пункт 1</option>
<option>Пункт 2</option>
</select>
есть серьёзные ограничения стилизации. Поэтому их заменяют на простые списки:
<ul>
<li>Пункт 1</li>
<li>Пункт 2</li>
</ul>
которым с помощью JavaScript навешивают весь необходимый функционал.
Поскольку изначально списки никак не взаимодействуют с формами, из-за этого возникают проблемы.
Мало того, что необходимо реализовать весь основной функционал:
- переключение пунктов и их взаимодействие
- состояния (checked, disabled)
- привязка к форме и отправка данных на сервер
- инициализация исходных данных (автозаполнение)
- сброс формы (при нажатии на кнопку «reset» список нужно вернуть в состояние при инициализации)
Фактически, если в JavaScript что-то ломается, не важно, из-за чего — скрипт не догрузился, возник баг, внесли правки — при возникновении любой проблемы выпадающий список превращается в… что-то, выглядящее как выпадающий список, но не работающее.
Вообще. Никак.
Для пользователя это будет выглядеть так: есть выпадающий список, но работать с ним нельзя.
Для сервера это будет выглядеть ещё «веселее» — пришла форма, в которой не хватает части данных. Хорошо, что такую ситуацию заранее предусмотрели при разработке… Ведь предусмотрели, правда?
Плюсы получившегося решения:
- Весь основной функционал (смотри выше), кроме сброса заголовка ВСЕГДА будет работать. Даже если у человека вообще отключён JavaScript (хоть это и стало практически неактуально). Единственное, что может пойти не так — нельзя будет открыть список. Но и в этом случае отправится значение по умолчанию, плюс смену состояний — список открыт/закрыт — можно реализовать прямо в коде html (защита от проблем с файлом JS), или вообще открывать по наведению.
- Данного функционала достаточно для большинства подобных списков. Да, возникнут проблемы у людей, использующих дополнительный функционал (об этом ниже). Но в случае со списком на ul — li для работы и вовсе требуется библиотека, и много что может пойти не так.
Минусы (доп. функционал, который не реализован, и добавляется JavaScript'ом / библиотеками):
- Переключение с помощью tab
- Управление стрелками (в стандартном select можно менять выбранный пункт кнопками вверх-вниз)
- Озвучка скринридерами (для людей, у которых проблемы со зрением текст на странице озвучивается)
- Не работает как нативный select на мобильных (на мобильных select довольно удобно работает сам по себе. При желании можно определять устройство и подменять на стандартный select)
- Список не умеет выпадать вверх, если снизу край вьюпорта (JS сделает это)
- Группировка пунктов (аналог OptGroup) (сложно, и очень редко встречается)
Как видно, ничто из вышеперечисленных минусов не является критичным для подавляющего большинства списков, и при необходимости реализуется с помощью JavaScript, а потому...
Добро пожаловать в мир, где Ваши библиотеки для стилизации Select-Option-подобных списков станут чуточку меньше, а работа с этими списками без библиотек — почти комфортной!
UPD: как оказалось, в случае с чекбоксами атрибут required работает некорректно, и его также необходимо эмулировать с помощью JavaScript (на данный момент обязательными становятся все чекбоксы, отмеченные как required, и только они)
KYuri
Стилизация — это хорошо.
Но доступность нужна не меньше.
Заменяя стандартные элементы управления самодельными, придется реализовывать штатные возможности самостоятельно.
В приведенных примерах без мыши/тачскрина сделать ничего нельзя. Скринридер тоже будет огорчен.
Kornerupin Автор
Хорошая мысль, большое спасибо.
Действительно не подумал об этом моменте, постараюсь придумать что-то, чтобы решить и эту задачу.
Kornerupin Автор
Тут важно отметить, что я пытался решить проблему стилизации, когда для этого используется простой список (ul, li).
Данный вариант куда лучше, поскольку всё вышеописанное реализовать проще, чем ещё и базовый функционал.