Стилизация скролл-бара средствами JavaScript
К чему всё это?
Тема дизайнерских изысков и пожеланий иногда находится за гранью добра и зла. Но от этого никуда не деться, бывает, что они хотят невозможного. Но я считаю, что можно сделать все (если это не противоречит законам физики), весь вопрос в сложности реализации, а соответственно в сроках решения и как итог — стоимость.
В 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;
}
Итог
Реализация, конечно, костыльная, и для мобильных устройств её стоит отключать, но вроде бы везде работает, так что можете пользоваться на здоровье. Кстати, есть небольшой пример вот тут. Пользуйтесь на здоровье =)
С началом нового рабочего года ;)