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