Стилизация и создание скриптов для ползунков

В настоящее время я работаю над своим первым оплачиваемым проектом. Помимо всего прочего в этом проекте от меня требуется стилизация и написание скриптов для ползунков. Есть несколько интересных моментов, на которые мне бы хотелось обратить ваше внимание, такие как проблемы отображения в IE, Android WebKit и правильное использование событий ввода и изменения. А также краткий обзор синтаксиса.

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

Ползунок, дорожка и бегунок

Вот ползунок. Технически он известен как , и он не поддерживается IE9 и ниже.

Здесь код HTML:

<input type="range" min="0" max="5" value="2" step="1">

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

Ползунок, дорожка и бегунок

Основы стилей

Стилизация ползунков не так уж трудна. Для бегунка и дорожки можно настроить стили, хотя необходимые псевдоэлементы зависят от браузера:

Для бегунка используется -webkit-slider-thumb, -moz-range-thumb или -ms-thumb.

К дорожке можно «обратиться» с помощью -webkit-slider-runnable-track, -moz-range-track или -ms-track.

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

IE и MS Edge требуют прозрачный color и border-color на дорожке, или они отобразят стили дорожки по умолчанию. Кроме того, для ползунка им нужны иные отступы, чем в других браузерах.

В моем примере высота ползунка примерно 40 пикселей. Оказывается, в основных стилях ползунка установлено свойство overflow: hidden, которое скрывает большую часть ползунка в Edge и IE. Чтобы исправить это, мне пришлось явно задать высоту ползунка.

В IE, но не в Edge, ползунок имеет отступ. Поэтому задайте отступ padding: 0 и спасите себя от головной боли.

Я читал несколько раз, что MS Edge должен поддерживать свойства -webkit-. Но на практике оказалось, что невозможно работать с одним набором стилей и для Edge, и для WebKit/Blink. Я советую использовать отдельные -ms- стили хотя бы потому, что они нужны для IE10 и IE11. Лучше всего определить стили -ms- после -webkit-: таким образом -ms- стили будут точно выполняться в Edge.

В браузерах, построенных на основе движков WebKit и Blink, бегунок имеет по умолчанию box-sizing: border-box, в то время как во всех остальных браузерах, значение этого свойства равно content-box. Лучше задать box-sizing для бегунка в явном виде.

WebKit и Blink также требуют -webkit-appearance: none, но с одним исключением. Смотрите ниже.

CSS шаблон

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

input[type=range] {
	-webkit-appearance: none;
	height: 35px; /* установите свою высоту*/
	padding: 0;	
	/* стили вашего ползунка */
}
input[type=range]::-webkit-slider-thumb {
	-webkit-appearance: none;
	box-sizing: content-box;
	/* стили вашего бегунка */
}
input[type=range]::-moz-range-thumb {
	/* снова стили вашего бегунка */
}
input[type=range]::-ms-thumb { /* должен следовать после -webkit- */
	/* снова стили вашего бегунка */
	/* могут потребоваться разные отступы */
}
input[type=range]::-webkit-slider-runnable-track {
	/* стили вашей дорожки*/
}
input[type=range]::-moz-range-track {
	/* снова стили вашей дорожки */
}
input[type=range]::-ms-track { /* должен следовать после -webkit- */
	border-color: transparent;
	color: transparent;
	/* снова стили вашей дорожки */
}

Свойство appearance и ошибка Android WebKit

Еще один недостаток в браузерах на основе WebKit и Blink заключается в том, что вы должны установить свойство -webkit-appearance: none для ползунка и для бегунка (но не для дорожки). Это показано в шаблоне CSS, приведенном выше.

Настоящая проблема в том, что Android WebKit требует от вас не использовать это объявление. Если вы добавите -webkit-appearance: none, Android WebKit будет неправильно отображать ваши стили. Удаление этого кода исправляет ошибку, но тогда стили ползунка не будут отображаться в Safari, Chrome и некоторых других браузерах. Решим эту проблему вместе.

Мы задали в CSS -webkit-appearance: none, это единственное решение, у которого есть будущее. В Android WebKit мы хотим изменить значение на slider-horizontal. Но как мы узнаем, что мы в Android WebKit?

Распознавание браузера? Так не принято в наших кругах – что и к лучшему: это не сработало бы в Xiaomi Chromium 34 (или 35), который базируется на Chromium и, следовательно, требует значение none, но при этом «маскируется» под Android.

Моим первоначальным планом было получить доступ к стилям бегунка и посмотреть, соответствуют ли они моим. Если они не подходят, то мы применяем slider-horizontal:

window.getComputedStyle(slider,'::-webkit-slider-thumb').height;

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

Затем я увидел, что это применение getComputedStyle() не поддерживается в Android WebKit. Ни один стиль не возвращается, даже стили по умолчанию.

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

var testAndWK = window.getComputedStyle(slider,'::-webkit-slider-thumb').height;
if (!testAndWK) {
	slider.style.WebkitAppearance = 'slider-horizontal';
}

Вы можете выполнить тест один раз и использовать результаты для каждого ползунка. Неважно, какое свойство CSS вы тестируете, проверьте, действительно ли оно существует. Если значение не возвращается, то устанавливаем -webkit-appearance: slider-horizontal для каждого ползунка. Проблема решена. Даже лучше, другие браузеры игнорирует нас, пока мы используем -webkit-.

Заполнение полосы прогресса

В моем примере дорожка слева от бегунка должна иметь цвет фона отличный от дорожки справа. IE/Edge и Firefox предлагают для этого использовать псевдоэлементы, WebKit и Blink – нет. Этот код решает проблему во всех версиях IE, Firefox и Edge:

input[type=range]::-ms-fill-lower {
	background-color: #5082e0;	
}
input[type=range]::-moz-range-progress {
	background-color: #5082e0;	
}

Что касается браузеров на основе WebKit- и Blink, то тут нужно решение с JavaScript. Я одолжил свое из примеров Ana Tudor. В этом решении используется линейный градиент со стоп-цветом, вычисляемым от текущего значения ползунка. Посмотрите скрипт, особенно код функции handleSlider().

Всплывающие подсказки

Ana Tudor также показала, как можно использовать псевдоэлемент :before в качестве стилизованной подсказки, содержащей текущее значение. К сожалению, выяснилось, что в этом случае IE/Edge и Firefox не поддерживают :before или :after. В IE/Edge есть встроенная подсказка (смотрите ниже), но в Firefox невозможно отобразить всплывающие подсказки.

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

В IE/Edge есть встроенная подсказка, которая по большей части не может быть стилизована. К счастью, вы можете скрыть ее:

input[type=range]::-ms-tooltip {
	display: none;
}

События ввода и изменения

Когда пользователь передвигает бегунок по дорожке, ползунок может вызывать события ввода или изменения. Одни браузеры непрерывно «порождают» события во время перемещения, другие – только после остановки перемещения.

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

slider.addEventListener('input',startTimer,false);
slider.addEventListener('change',startTimer,false);
var timeout;
function startTimer() {
	var that = this;
	clearTimeout(timeout);
	timeout = setTimeout(function () {
		handleEvent(that);
	},100);
}
function handleEvent(slider) {
	/* Срабатывает, когда пользователь не перемещает бегунок в течение 100 мс*/
	...
}

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