Сколько я не мучил поисковик, а решения этого вопроса так и не нашлось. Конечно, всегда можно использовать JS и это нормально, но иногда заказчик душа просит изысков.
В заголовке я несколько приврал: всем известно, что select полностью кастомизировать нельзя, поэтому мы будем имитировать select. Сделаем мы это с помощью нескольких radio, нескольких label и одного div. Не так уж и много, правда?
Корневая label будет всегда видимой частью нашего альтернативного select. При клике на нее будет переключаться основной radio, который и отвечает за состояния открыт/закрыт у этой конструкции. Placeholder и традиционная стрелочка будут реализованы через псевдоэлементы :before и :after у корневой label. Все остальное, кроме wrapper (тот самый единственный div), по умолчанию скрыто. Почему мы не скрываем wrapper? Потому что в нем находится выбранный элемент (если такой есть), а он должен быть виден всегда.
Осталось добавить немного магии — реализовать поведение всего этого добра. Магия будет основана на соседних селекторах и :checked у radio. Выбранный элемент виден всегда и при закрытом состоянии select перекрывает собой placeholder. При открытии select, показываются все остальные элементы для выбора, а wrapper, в который они вложены, немного съезжает вниз, что-бы было видно placeholder и пользователь не забыл, что же он, собственно, выбирает.
В конце применен трюк с z-index, который позволяет расположить дочерний элемент ниже (глубже по z-оси) родительского. Этот замечательный факт позволяет делегировать реакцию на клик по выбранному элементу нашему select'у, что бы он раскрылся.
Рабочий пример можно лицезреть тут.
Из плюсов подхода можно отметить:
Конечно же, есть и минусы, куда же без них:
Не думаю, что кто-то станет использовать это в production, но подход явно имеет право на жизнь.
UDPATE: Изменен код — добавлена функция автоматического закрытия select при выборе.
В заголовке я несколько приврал: всем известно, что select полностью кастомизировать нельзя, поэтому мы будем имитировать select. Сделаем мы это с помощью нескольких radio, нескольких label и одного div. Не так уж и много, правда?
Структура
<!-- Вот эта штука и заменит нам select -->
<label class="selectGeneral" placeholder="select your OS..."> <!-- Да, да, placeholder тоже поддерживается -->
<!-- Если этот radio выбран - select открыт, если нет - select закрыт -->
<input type="radio" name="OS">
<!-- Это wrapper для вариантов выбора -->
<div>
<!-- Группа radio и есть аналог оригинальных option -->
<input
type="radio"
name="OS"
value="linux"
id="OS[linux]"
>
<!-- Аналог видимой части option -->
<label for="OS[linux]">linux</label>
<input
type="radio"
name="OS"
value="windows"
id="OS[windows]"
>
<label for="OS[windows]">windows</label>
<input
type="radio"
name="OS"
value="other"
id="OS[other]"
>
<label for="OS[other]">other</label>
</div>
</label>
Корневая label будет всегда видимой частью нашего альтернативного select. При клике на нее будет переключаться основной radio, который и отвечает за состояния открыт/закрыт у этой конструкции. Placeholder и традиционная стрелочка будут реализованы через псевдоэлементы :before и :after у корневой label. Все остальное, кроме wrapper (тот самый единственный div), по умолчанию скрыто. Почему мы не скрываем wrapper? Потому что в нем находится выбранный элемент (если такой есть), а он должен быть виден всегда.
Основная часть
label.selectGeneral
{
display: block;
position: relative;
}
/** Это обещанный placeholder **/
label.selectGeneral:before
{
content: attr(placeholder); /** Взять текст из атрибута placeholder **/
display: inline-block;
position: absolute;
top: 0;
left: 0;
z-index: -1;
max-width: 100%;
text-align: left;
white-space: nowrap; /** Не переносить слова **/
color: #adadad;
overflow-x: hidden; /** Скрыть лишнее **/
}
/** А это стрелочка **/
label.selectGeneral:after
{
content: "<>";
display: inline-block;
position: absolute;
top: 0;
right: 0;
text-align: center;
background-color: #ffffff;
transform: rotate(90deg);
}
label.selectGeneral input,
label.selectGeneral label
{
display: none;
}
label.selectGeneral div
{
min-width: 100%;
max-height: 500px; /** Ограничения на высоту списка выборов **/
overflow-x: hidden;
}
Осталось добавить немного магии — реализовать поведение всего этого добра. Магия будет основана на соседних селекторах и :checked у radio. Выбранный элемент виден всегда и при закрытом состоянии select перекрывает собой placeholder. При открытии select, показываются все остальные элементы для выбора, а wrapper, в который они вложены, немного съезжает вниз, что-бы было видно placeholder и пользователь не забыл, что же он, собственно, выбирает.
Поведение
/** Если наш альтернативный select открыт, то wrapper **/
label.selectGeneral input[type="radio"]:checked ~ div
{
position: absolute; /** приобретает абсолютную позицию **/
top: <высота label.selectGeneral>; /** и смешается немного вниз, открывая placeholder **/
overflow-y: auto;
}
/** Все label внутри wrapper'а при открытом select **/
label.selectGeneral input[type="radio"]:checked ~ div > label,
/** И выбранный вариант **/
label.selectGeneral input[type="radio"]:checked + label
{
display: block; /** должны быть видимыми **/
}
/** Подсветим элемент на который наведена мышь при открытом select **/
label.selectGeneral input[type="radio"]:checked ~ div > label:hover
{
background-color: #ffa834;
}
/** При закрытом select, нужно делегировать событие клика мышью с выбранного элемента родительскому label **/
label.selectGeneral input[type="radio"]:not(:checked) ~ div > input[type="radio"]:checked + label
{
position: relative;
z-index: -1;
}
В конце применен трюк с z-index, который позволяет расположить дочерний элемент ниже (глубже по z-оси) родительского. Этот замечательный факт позволяет делегировать реакцию на клик по выбранному элементу нашему select'у, что бы он раскрылся.
Рабочий пример можно лицезреть тут.
Из плюсов подхода можно отметить:
- Кроссбраузерность — это работает везде, где работают label
- Относительная легкость — код не перенасыщен лишними элементами
- Возможность полной кастомизацией — стилизуется каждая мелочь
- Гибкость — не придется дописывать новых стилей при добавлении пунктов выбора
Конечно же, есть и минусы, куда же без них:
- Отсутствие деградации — если не поддерживается, то стандартный select не спасет ситуацию (как подсказывают комментаторы — очень важный пункт для портативных устройств)
- Легкость легкостью, а дополнительный код все таки будет
- Невалидный код — div внутри label и атрибут placeholder у нее же, это не по стандарту
- Это не совсем минус, но эта штука не захлопывается сама при клике в любом месте экрана
Не думаю, что кто-то станет использовать это в production, но подход явно имеет право на жизнь.
UDPATE: Изменен код — добавлена функция автоматического закрытия select при выборе.