- PVSM.RU - https://www.pvsm.ru -
Статья посвещена решению проблемы кастомизации скроллбаров браузера ради воплощения в жизнь амбициозных идей дизайнера. Статья расчитана на тех, кто свободно ориентируется в технологиях html, css, js, т.к. предлагаемое решение основано на их компромиссном использовании.
В статье будут описаны и решены следующие задачи и цели:
Чтобы было от чего отталкиваться, я приведу простой пример (посмотреть в работе [1]):
<div style="border: 3px solid #f08080; height: 300px; overflow: auto; width: 300px;">
<div style="background-color: #90ee90; border: 10px solid #4169e1; min-height: 600px; min-width: 600px;">
content...
</div>
</div>
Браузер для навигации по контенту в такой области предоставляет следующие возможности (советую попробовать все из них):
Как можно заметить, набор возможностей весьма большой и он весьма хорошо реализован, продуман, удобен и привычен для конечного пользователя…
Стоит ли это все или часть этого реализовывать средствами js, чтобы изменить внешний вид скроллбаров?
да: потому что есть новые идеи для навигации по контенту, тяга к экспериментам с js и т.д.
нет: браузер уже все это делает и нужно найти способ, как это использовать. Вот об этом я и расскажу ниже.
Итак.
Решение имеет следующую структуру на html:
<div class="scrollar">
<div class="scrollar-viewport">
<div class="scrollar-systemscrolls">
<div class="scrollar-contentwrap">
<div class="scrollar-content"></div>
</div>
</div>
</div>
<div class="scrollar-scroll scrollar-vscroll">
<div class="scrollar-btn scrollar-btnup"></div>
<div class="scrollar-btn scrollar-btndown"></div>
<div class="scrollar-track scrollar-vtrack">
<div class="scrollar-thumb scrollar-vthumb"></div>
</div>
</div>
<div class="scrollar-scroll scrollar-hscroll">
<div class="scrollar-btn scrollar-btnleft"></div>
<div class="scrollar-btn scrollar-btnright"></div>
<div class="scrollar-track scrollar-htrack">
<div class="scrollar-thumb scrollar-hthumb"></div>
</div>
</div>
<div class="scrollar-corner"></div>
</div>
Scrollar — одновременно название решения и namespace, чтобы исключить пересечение стилей на целевой странице, где решение может быть использовано.
Чтобы было понятнее — поясню структуру:
А теперь можно приступить к рассмотрению ключевых моментов...
На мой взгляд, это самые «тяжелые» и неудобные в реализации на javascript операции. Почему? Чтобы программно реализовать Resize и сохранение пропорций нужно, в основном, обрабатывать событие window.onresize, а во время возникновения этого события — корректировать размеры и пропорции у нескольких элементов (чаще всего так…). Недостатком ресайза подобным образом является неплавное (с небольшими непостоянными, заметными глазу, шагами) изменение размеров элемента, кто пытался подобное отладить — поймет меня. Это сильно «напрягает» глаза, когда вкладываешь душу в разработку и стараешься довести работу всего интерфейса до идела.
Таким образом, чтобы сохранить максимальную плавность при изменении размеров элементов стоит использовать один из вариантов резиновой верстки блочных элементов с абсолютным позиционированием относительно друг друга и изменением размеров элементов за счет привязки к определенным координатам:
.scrollar, .scrollar-viewport, .scrollar-systemscrolls, .scrollar-contentwrap, .scrollar-content {
bottom: 0px;
height: auto;
left: 0px;
position: absolute;
right: 0px;
top: 0px;
width: auto;
}
/* корректировка стиля корневого элемента */
.scrollar { overflow: visible; position: relative; }
/* эти блоки не должны иметь привязки справа и снизу */
.scrollar-contentwrap, .scrollar-content {
bottom: auto;
right: auto;
}
после объявления таких стилей браузер будет сам изменять размеры как области прокрутки, так и внутренних элементов. Ниже по статье, стили некоторых элементов будут дополнены и скорректированы, чтобы добиться нужного и лучшего результата.
О Scroll. Для реализации scroll на desktop-браузерах необходимо обрабатывать событие колеса мыши и анализировать значения от этого события (также не забывать, что некоторые мыши позволяют листать контент в горизонтальной плоскости, а не только в вертикальной), а для mobile-браузеров нужно обрабатывать события группы touch. Т.е. для кроссплатформенного решения нужно программировать две эти реализации. Но лучше прокрутку контента возложить на сам браузер. Достаточно определить стиль для элемента systemscrolls:
.scrollar-systemscrolls { overflow: scroll; }
Решая задачу прокрутки контента, я использовал свойство overflow:scroll, которое заставляет браузер отображать скроллбары всегда и тем самым предоставляет пользователю все удобства системной прокрутки. Но теперь нужно эти скроллбары скрыть. Здесь как раз и выручит viewport — этот блок будет скрывать всё, что выходит за его пределы. Это можно сделать двумя способами:
.scrollar-viewport { overflow: scroll; }
.scrollar-viewport { clip: rect(0, auto, auto, 0); clip: rect(0 100% 100% 0); }
Первый вариант с overflow прост для понимания, но когда пользователь захочет выделить контент и начнет тянуть курсор в нужную сторону, то он, вполне вероятно, увидит системные скроллбары, т.к. при таком действии они вылезут из-под скрываемой области. Вариант с clip таким не страдает, но в этом случае пришлось применить небольшой хак и для поддержки ie7. Но это ещё не всё… Блок systemscrolls имеет такие же размеры, как и блок viewport, т.е. системные скроллбары еще видны. Здесь и используется ключевой момент «22px» — это величина, на которую будет скорректирован блок systemscrolls. Дело в том, что толщина скроллбаров у популярных браузеров менее 21px. Сама корректировка выглядит так:
.scrollar-systemscrolls {
bottom: -22px;
right: -22px;
}
После этого скроллбары будут скрыты и будут находиться за границами той области, которая обрезана с помощью clip.
И что в итоге? Браузер сам изменяет и следит за размерами всего элемента, контент легко и плавно прокручивается всеми описанными выше способами, а системные скроллбары скрыты. Но если это оставить в таком виде, то часть контента справа и снизу отображаться не будет…
Основное и главное назначение блока contentwrap — это сделать так, чтобы в блоке viewport можно было увидеть блок content полностью: от одного края до другого при разных способах прокрутки.
До этого момента javascript не требовался, но сейчас он пригодится для того, чтобы скорректировать размеры блока contentwrap.
…
var viewport = $(".scrollar-viewport", scrollar);
var systemscrolls = $(".scrollar-systemscrolls", scrollar);
var correct_h = systemscrolls[0].clientHeight - viewport.height(); // корректировка по высоте
var correct_w = systemscrolls[0].clientWidth - viewport.width(); // корректировка по ширине
…
таким образом, размеры элемента contentwrap будут получаться из сложения этих величин с размерами блока content, и делать это будет нужно при каждом изменении размеров блока content. Есть исключения, но о них будет рассказано ниже.
Корректировка блока contentwrap с помощью js позволяет не обращать внимания на вид и версию браузера и используемую им толщину скроллбаров.
vscroll и hscroll — скроллбары. На данный момент, основная задача — «приклеить» их к краям и заставить их изменять свои размеры и местоположение их дочерних элементов за счет браузера:
.scrollar-scroll,
.scrollar-track,
.scrollar-btn,
.scrollar-thumb,
.scrollar-corner {
position: absolute;
}
.scrollar-hscroll {
bottom: 0px;
height: 0px;
left: 0px;
right: 0px;
}
.scrollar-vscroll {
bottom: 0px;
right: 0px;
top: 0px;
width: 0px;
}
.scrollar-btnup { left: 0px; top: 0px; }
.scrollar-btndown { bottom: 0px; left: 0px; }
.scrollar-btnleft { left: 0px; top: 0px; }
.scrollar-btnright { right: 0px; top: 0px; }
.scrollar-vthumb { max-height: 100%; height: 30px; left: 0px; right: 0px; }
.scrollar-hthumb { max-width: 100%; width: 30px; bottom: 0px; top: 0px; }
в этом листинге нет ничего сложного и я перейду к более интересной части: бегунки.
Для успешной реализации функционала бегунков нужно рассмотреть следующие задачи:
Это сделать крайне просто. Благодаря установленному свойству overflow:scroll у блока systemscrolls можно ловить собитие scroll на этом блоке, а уже при возникновении этого события двигать бегунок в зависимости от положения (свойства scrollLeft и scrollTop) контента относительно левой верхней точки блока systemscrolls с учетом коэффициента пропорциональности, который вычисляется в функции обновления параметров компонента (об этом будет ниже).
Бегунок должен реагировать на поведение указателя мыши также, как и в системе. Сделать это весьма не сложно. Алгоритм заключается в следующем:
Как можно заметить, алгоритм весьма простой, но есть один нюанс: изменение положения бегунков, точнее алгоритм изменения этого положения. Менять положение бегунков можно двумя способами:
На первый взгляд, первый способ кажется лучше, т.к. требует меньше усилий для реализации. Но недостатком этого способа является присутствующая обратная связь через событие systemscrolls.scroll (см. рисунок), из-за которой бегунок начинает заметно отставать от указателя мыши при быстром перемещении последнего. При этом, событие systemscrolls.scroll возникает всякий раз при изменении свойств scrollLeft и scrollTop у блока systemscrolls, вызывая функцию перемещения бегунков.
При втором способе можно получить гораздо лучший результат. Дополнительные операции по отсоединению и присоединению события systemscrolls.scroll происходят только два раза: на события mousemove и mouseup (соответственно) элемента document. Таким образом, обработка события document.mousemove происходить быстрее и оптимальнее (см. рисунок)
Вот и дошло время до весьма важной функции — обновление параметров компонента. Для этой функции необходимо обеспечить скорость выполнения, т.к. она может вызываться при ресайзе и смене контента очень часто, поэтому большая часть её написана на чистом js. Ниже приведен кусок кода для обновления параметров по горизонтали:
// Горизонтальный скрол
if (options.hscroll) {
scroll = env.hscroll;
var ss_cw = ss.clientWidth;
// Корректировка ширины contentwrap
cw.style.width = (ct.offsetWidth + env.correct_w) + "px";
while (Math.abs(cw.scrollWidth - ct.offsetWidth) <= 1) {
cw.style.width = (ct.offsetWidth + 1000) + "px";
cw.style.width = (ct.offsetWidth + env.correct_w) + "px";
}
var ss_sw = ss.scrollWidth;
// Ширина горизонтального ползунка
var htrack_w = scroll.track.width();
var hthumb_w = htrack_w * ss_cw / ss_sw;
if (hthumb_w > htrack_w) {
hthumb_w = htrack_w;
} else if (hthumb_w < 30) {
hthumb_w = 30;
}
scroll.thumb.outerWidth(hthumb_w);
// Коэффициенты пропорциональности
x = htrack_w - hthumb_w;
scroll.ratio = (ss_sw - ss_cw) / (x < 1 ? 1 : x);
if (scroll.ratio < 1) scroll.ratio = 1;
// Крайние положение ползунка
scroll.min_pos = 0;
scroll.max_pos = htrack_w - hthumb_w;
// Корректировка положения ползунка
scroll.thumb.css("left", ss.scrollLeft / scroll.ratio);
}
Из этого листинга хочу уделить внимание именно куску кода «Корректировка ширины contentwrap», остальное понятно и без объяснения. Смысл этого куска в следующем:
Когда нужно обновлять параметры компонента?
Если первые два случая весьма прозрачны, то вот последний таит в себе подводные камни. Контент может меняться не только во время удаления или добавления каких-либо элементов, но и по завершению загрузки каких-нибудь картинок, если их размеры не заданы заранее, и т.д. Можно отлавливать все варианты событий onload, но это не стоит того. Самое оптимальное решение — setInterval(update, 300) — функция update будет запускаться каждые 300 мс, нагрузки на браузер почти никакой и все весьма надежно.
Кастомизация — это то, ради чего всё это и затевалось. Целью было: свести к минимуму усилия верстальщика и сэкономить время. После нескольких экспериментов со структурой элементов в скроллбаре приведенная ниже структура является более гибкой:
<div class="scrollar-scroll scrollar-vscroll">
<div class="scrollar-btn scrollar-btnup"></div>
<div class="scrollar-btn scrollar-btndown"></div>
<div class="scrollar-track scrollar-vtrack">
<div class="scrollar-thumb scrollar-vthumb"></div>
</div>
</div>
Эта верстка вертикального скроллбара (для горизонтального всё подобно). Преимущество такой структуры следующие:
По сути, вся кастомизация сводится к написанию стилей. В конце статьи будут приведены ссылки на примеры кастомизации, а также ссылки на файлы стилей для этих примеров.
Все просто: при инициализации компонента нужно указать какой скрол не нужен (по умолчанию — оба отображаются). Управление видимостью и скролов производится с помощью добавления или удаления классов. Также добавление этих классов влияет на размеры контента (например, при hscroll: false ширина контента будет меняться автоматически за счет нативного функционала браузера)
Есть функция content(«action», content), где action — функция jQuery по управлению содержимым (text, html, append, prepend…). Также эту функцию можно вызвать без параметров, тогда она вернет объект jQuery и с ним можно работать, в этом случае параметры компонента будут обновляться каждые 300 мс. Примеры:
Customization [2] — на этой странице можно посмотреть несколько вариантов кастомизации, а также можно опробовать реакцию элемента прокрутки на изменение размеров окна браузера.
GitHub [3] — репозиторий, где можно найти всё, что описывалось в статье
Default style [4] — пример произвольной стилизации
Safari style [5] — стилизация скроллбаров в стиле Mac OS 10.8
Стоит ли изобретать что-то новое? Безусловно! У каждой новой идеи есть шанс повлиять на будущее (в конкретном случае — будущее юзабилити)
Автор: ALeutsky
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/33283
Ссылки в тексте:
[1] посмотреть в работе: http://ui4.me/project/scrollar/examples/intro.html
[2] Customization: http://ui4.me/project/scrollar/examples/customization.html
[3] GitHub: https://github.com/ALeutsky/Scrollar
[4] Default style: https://github.com/ALeutsky/Scrollar/blob/master/scrollar/scrollar.default.style.css
[5] Safari style: https://github.com/ALeutsky/Scrollar/blob/master/scrollar/scrollar.safari.style.css
[6] Источник: http://habrahabr.ru/post/178299/
Нажмите здесь для печати.