Статьи

Заставить выпадающие списки HTML не сосать

Настройка элементов формы всегда была довольно неприятной, и элемент select — один из тех элементов, который может заставить вас захотеть вырвать оставшиеся волосы, пытаясь их стилизовать. Как вы, возможно, уже знаете, когда речь идет о его настройке, к нему можно применить не так уж много CSS-элементов, а только такие свойства, как цвет, фон, шрифт или рамку.

В этой статье, чтобы настроить внешний вид выпадающего списка HTML, мы будем использовать другой подход, основанный на некоторых передовых технологиях, таких как @supports, pointer-eventsи appearance.

Пользовательский выпадающий предварительный просмотр

Посмотреть демо

Необходимость пользовательских выпадающих

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

Имея это в виду и вдохновленный техникой Леа Веру , я решил поделиться с вами своим мнением о настройке раскрывающегося списка HTML. Так же, как заявление об отказе от ответственности, я знаю, что в дикой природе есть множество удивительных плагинов jQuery, которые могут помочь вам создавать красивые и сложные выпадающие списки, но иногда кажется, что это слишком много для раскрывающегося списка.

Разметка

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

<span class="custom-dropdown custom-dropdown--white">
    <select class="custom-dropdown__select custom-dropdown__select--white">
        <option>The Shawshank Redemption</option>
        <option>The Godfather</option>
        <option>Pulp Fiction</option>
        <option>The Good, the Bad and the Ugly</option>
        <option>12 Angry Men</option>
    </select>
</span>

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

<label>
	IMDB Top Movies:
	<span class="custom-dropdown custom-dropdown--white">
	    <select class="custom-dropdown__select custom-dropdown__select--white">
	        <option>The Shawshank Redemption</option>
	        <option>The Godfather</option>
	        <option>Pulp Fiction</option>
	        <option>The Good, the Bad and the Ugly</option>
	        <option>12 Angry Men</option>
	    </select>
	</span>
</label>

Соглашение об именах БЭМ

Если вы заметили пространство имен, я использую соглашение об именах БЭМ, которое, я уверен, вы видели раньше. Излишне говорить, что это камни!

(Block) .custom-dropdown		= The main component
(Element) .custom-dropdown__select	= Descendant of .custom-dropdown
(Modifier) .custom-dropdown--*		= Different state of .custom-dropdown

CSS

Пользовательский раскрывающийся список деконструирован

Проверьте стили ниже, чтобы увидеть, как все работает. @supportsПравило делает всю магию:

.custom-dropdown--large {
	font-size: 1.5em;
}

.custom-dropdown--small {
	font-size: .7em;
}

.custom-dropdown__select{
	font-size: inherit; /* inherit size from .custom-dropdown */
	padding: .5em; /* add some space*/
	margin: 0; /* remove default margins */
}

.custom-dropdown__select--white {
	background-color: #fff;
	color: #444;    
}

@supports (pointer-events: none) and
	  ((-webkit-appearance: none) or
	  (-moz-appearance: none) or
	  (appearance: none)) {

	.custom-dropdown {
		position: relative;
		display: inline-block;
		vertical-align: middle;
	}

	.custom-dropdown__select {
		padding-right: 2.5em; /* accommodate with the pseudo elements for the dropdown arrow */
		border: 0;
		border-radius: 3px;
		-webkit-appearance: none;
		-moz-appearance: none;
		appearance: none;    
	}

	.custom-dropdown::before,
	.custom-dropdown::after {
		content: "";
		position: absolute;
		pointer-events: none;
	}

	.custom-dropdown::after { /*  Custom dropdown arrow */
		content: "\25BC";
		height: 1em;
		font-size: .625em;
		line-height: 1;
		right: 1.2em;
		top: 50%; margin-top: -.5em;
	}

	.custom-dropdown::before { /*  Custom dropdown arrow cover */
		width: 2em;
		right: 0; top: 0; bottom: 0;
		border-radius: 0 3px 3px 0;
	}

	.custom-dropdown__select[disabled] {
		color: rgba(0,0,0,.3);
	}

	.custom-dropdown.custom-dropdown--disabled::after {
		color: rgba(0,0,0,.1);
	}

	/* White dropdown style */
	.custom-dropdown--white::before {
		top: .5em; bottom: .5em;
		background-color: #fff;
		border-left: 1px solid rgba(0,0,0,.1);
	}

	.custom-dropdown--white::after {
		color: rgba(0,0,0,.9);
	}

	/* FF only temp fix */
	@-moz-document url-prefix() {
		.custom-dropdown__select 			 { padding-right: .9em }
		.custom-dropdown--large .custom-dropdown__select { padding-right: 1.3em }
		.custom-dropdown--small .custom-dropdown__select { padding-right: .5em }
	}
}

Резюме

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

внешность

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

Возможно, вы видели выше это правило CSS: @-moz-document url-prefix()ну, это хак для браузеров Firefox . В Firefox есть довольно старая ошибка, связанная с тем, как она appearance: noneработает , но она заключается в том, что, хотя appearance: noneкажется, что она работает, нативная стрелка раскрывающегося списка все еще показывает.

Итак, временное решение состоит в том, чтобы в основном покрыть нативную стрелку раскрывающегося списка:

@-moz-document url-prefix() {
  ...
}

::до после

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

указатель событий

Если вы не знакомы с этим pointer-events, вы должны знать, что с pointer-events: noneпомощью наиболее часто встречающегося объявления вы можете, например, удалить события мыши для навигации по текущей странице. В этом случае мы удалили события мыши для псевдоэлемента прямоугольного треугольника, чтобы избежать проблем с доступностью.

В качестве побочного эффекта, если элемент был pointer-events: noneприменен, его состояние наведения больше не может быть стилизовано.

@supports

И последнее, но не менее важное: последнее @supportsправило помогает обнаруживать функции CSS. Обнаружение функций с использованием JavaScript не является чем-то новым, и Modernizr — лучший пример здесь. Итак, если вы уже слышали о Modernizr, подумайте о @supportsправиле как о его CSS-эквиваленте.

Чтобы предотвратить несоответствия браузеров, следующее правило позволяет настраивать таргетинг только на те браузеры, которые поддерживают оба pointer-eventsварианта appearance. Лучшими примерами здесь являются браузеры IE9 и IE10, которые поддерживают множество CSS3-компонентов из приведенного выше кода, но не поддерживают (пока) передовые технологии, такие как pointer-eventsили appearanceкоторые делают эту технику неэффективной.

@supports (pointer-events: none) and
	((-webkit-appearance: none) or
	 (-moz-appearance: none) or
	 (appearance: none)) {
   ...
}

Немного JavaScript

Правда заключается в том, что вы можете использовать только ключевое слово CSS, чтобы назвать это решение по стилю. Но, чтобы охватить также случай, когда выборка отключена, вам понадобится JS, чтобы нацелиться на родительский узел и применить класс HTML, например custom-dropdown--disabled.

<script>
(function(){
	/*1*/var customSelects = document.querySelectorAll(".custom-dropdown__select");
	/*2*/for(var i=0; i<customSelects.length; i++){
		if (customSelects[i].hasAttribute("disabled")){
			customSelects[i].parentNode.className += " custom-dropdown--disabled";
		}
	}	
})()
</script>

  1. Возвращает список элементов в документе, которые совпадают .custom-dropdown__select.
  2. Для каждого отключенного выбора перейдите на его родительский узел и добавьте custom-dropdown--disabledкласс HTML. Таким образом, мы сможем использовать CSS для настройки стрелки, когда выбор отключен.

Конечно, ничего из вышеперечисленного не потребуется, если элемент select, который является заменяемым элементом , разрешил бы на нем псевдоэлементы или если бы существовал родительский селектор CSS . Но это другая история. ?

На поддержку браузера

Насколько я тестировал, демо работает на Firefox Nightly / Aurora , Chrome Canary и Opera Next . Кроме того, это означает, что очень скоро вы сможете увидеть, как это работает и в версиях выпуска, учитывая, как часто запускаются новые версии браузера. Проверьте таблицу совместимости для поддержки CSS Feature Queries в настольных и мобильных браузерах.

Посмотреть демо

Помимо вышеупомянутых версий браузеров, при подготовке к публикации этой статьи, Firefox 22 только что запустил. И угадайте, что? Поддержка условных выражений CSS (@supports и CSS.supports) включена по умолчанию. Итак, мы на правильном пути! ?