Стилизация скролл-бара средствами JavaScript

Ильдар Сарибжанов | 09.01.2017

К чему всё это?

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

В web’е есть ряд вещей, которые не поддаются стилизации в классическом понимании, т.е. нельзя, используя только CSS, изменить их внешний вид. К таким элементам относятся многие теги формы: селекты, чекбоксы, радио-кнопки. Ранее я уже рассказывал, как стилизовать select. Теперь поговорим, как сделать красивый скролл-бар. Откровенно говоря, я ненавижу такие задачи, которые решаются не очевидно или не стандартно, потому что от браузера к браузеру, или даже от версии к версии браузера или ОС, могут вести себя по разному. Хватит лирики, работаем.

Немного теории

Полная эмуляция на js мне не нравится, т.к. это сложно и если js падает, то возможность пролистывания блока пропадает совсем. Где-то давно я почерпнул следующую идею:

Черный прямоугольник — это область просмотра, например, окно браузера.
Зеленый прямоугольник — это то, что должно прокручиваться. Таким образом, ширина прокручиваемого блока шире области просмотра на ширину скролла, примерно, лучше больше, т.к. в разных браузерах, ширина полосы прокрутки разная. Теперь строим из div’ов визуальное представление скролл-бара и вешаем на него нужные обработчики. Теперь можем задать любой мыслимый стиль для лемента, и если js у пользователя отключен, либо в результате ошибки, он упадет, то пролистать блок все равно будет можно.

Практика

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

Собственно, код обертки такой:

(function ($) {
	$.fn.wrapFoBaron = function (options) {
		options = $.extend({
			root    : '.scroller_wrap',
			barOnCls: 'baron'
		}, options);

		var make = function () {

			$(document).ready(function () {

				$(options.root).each(function () {
					$(this).wrapInner('<div class="scroller__content"></div>');
					$(this).wrapInner('<div class="scroller"></div>');
					$(this).append('<div class="scroller__bar-wrapper"><div class="scroller__bar"></div></div>');
				});

				var params = {
					root    : options.root,
					scroller: '.scroller',
					bar     : '.scroller__bar',
					barOnCls: options.barOnCls
				};

				var scroll = baron(params);

				check_size();
				check_size();

				function check_size() {
					$(options.root).each(function () {
						if ($(this).find('.scroller__content').height() <= $(this).height()) {
							$(this).find('.scroller__bar-wrapper').hide();
							$(this).find('.scroller').addClass('.with-scroll');
						} else {
							$(this).find('.scroller').removeClass('.with-scroll');
						}
					});
				}
			})
		};

		return make();
	};
})(jQuery);

И вызываем вот так

$(document).wrapFoBaron({
	root: '.class-name',
	barOnCls: 'baron'
});

Я даже немного попытался соответствовать идеологии БЭМ. Глядя на код сразу бросается в глаза, что функция check_size вызывается 2 раза подряд, зачем так делается и вообще зачем она? А затем, что кастомный скролл-бар добавляется только тогда, когда это реально необходимо, это ответ на второй вопрос, а на первый вопрос ответ такой: После того как было принято решение, что надо добавить скролл-бар, запускается процесс добавления разметки и в этот момент размеры наружных блоков могут (с большой вероятностью они это сделают) изменится, и чтобы все выглядело адекватно, запускаем процесс инициализации дважды.

Чтобы было проще втянутся в процесс понимания, приведу тут пример стилей одного из наших проектов

.scroller {
	height: 100%;
	overflow-y: auto;
	-webkit-box-sizing: border-box;
	-moz-box-sizing: border-box;
	box-sizing: border-box;
}

.scroller::-webkit-scrollbar {
	width: 0;
}

.scroller__content {
	position: relative;
	padding-right: 30px;
	overflow: hidden;
}

.scroller__bar-wrapper {
	position: absolute;
	z-index: 2;
	top: 10px;
	bottom: 10px;
	right: 5px;
	width: 8px;
	border-radius: 5px;
	background-color: rgba(255, 255, 255, 0.5);
	pointer-events: none;
	-webkit-transition: all 0.2s linear 0s;
	-moz-transition: all 0.2s linear 0s;
	-o-transition: all 0.2s linear 0s;
	transition: all 0.2s linear 0s;
	opacity: 0;
}

.scroller__bar {
	position: absolute;
	z-index: 1;
	width: 8px;
	border-radius: 3px;
	background-color: rgba(0, 0, 0, 0.5);
	-webkit-transition: opacity 0.2s linear 0s;
	-moz-transition: opacity 0.2s linear 0s;
	-o-transition: opacity 0.2s linear 0s;
	transition: opacity 0.2s linear 0s;
	pointer-events: auto;
	opacity: 0;
}

.baron:hover .scroller__bar-wrapper {
	opacity: 1;
}

.baron:hover .scroller__bar {
	opacity: 1;
}

Итог

Реализация, конечно, костыльная, и для мобильных устройств её стоит отключать, но вроде бы везде работает, так что можете пользоваться на здоровье. Кстати, есть небольшой пример вот тут. Пользуйтесь на здоровье =)

С началом нового рабочего года ;)