Прокрутка контента внутри блока на Javascrip.

Вступление.

При создании сайта очень часто возникает необходимость реализовать вертикальную прокрутку контента внутри блока <DIV>. Многие из вас скажут, что нет ничего проще, достаточно установить фиксированную высоту блока и задать его свойству overflow-y значение auto или scroll. Действительно, этого вполне достаточно, но, в большинстве случаев, полосы прокрутки нарушают дизайн веб-страницы.

В этой статье я расскажу вам, как сделать прокрутку контента в блоке и сам скроллбар c помощью JavaScript двумя способами — используя события wheel и scroll. Будут подробно рассмотрены HTML-вёрстка, CSS и сам код на чистом JavaScript.

Пример блока с вертикальной прокруткой и кастомным скроллбаром:

Прокрутка контента внутри блока на JavaScript

Составим техническое задание на создание прокрутки контента внутри блока:

  1. Прокрутка должна осуществляется колёсиком мыши при наведении на выбранный блок или перетаскиванием ползунка скроллбара.
  2. Блок должен иметь кастомный скроллбар с высотой ползунка, зависящей от размера контента внутри блока.
  3. При прокрутке контента в блоке использовать на выбор:
    — событие вращения колёсика мыши wheel;
    — стандартное событие scroll, возникающее при прокрутке блока.
  4. На странице может быть несколько блоков с прокруткой контента.
  5. Ширина и высота блоков с прокруткой не должна влиять на работу скрипта.

HTML-вёрстка, стили и JavaScript для обоих вариантов прокрутки блока совпадают, поэтому я подробно расскажу о прокрутки блока по события wheel, а потом рассмотрим особенности и отличия управления прокруткой по событию scroll.

HTML-разметка блока с прокруткой.

Давайте сверстаем блок, как представлен на картинке выше. Он будет состоять из трёх основных частей:

  1. Блок заголовка.
  2. Вьпорт, где будет прокручиваться контент. В нашем случае — это нумерованный список.
  3. Подвал (футер).

Кратко рассмотрим назначение каждого элемента:

<div class=’container’></div>
Родительский элемент, собственно, блок с прокруткой по вертикали. Задаёт ширину и высоту кострукции. Имеет атрибут data-control, по значению которого, мы можем определить, какой способ прокрутки будет реализован.
<div class=’container-title flex-middle’></div>
Заголовок блока. Этот элемент необязательный, можно обойтись без него.
<div class=’viewport-wrapper’></div>
Родительский элемент для вьюпорта, в котором будет прокручиваться контент. Относительно его границ будет позиционироваться вьюпорт.
<div class=’viewport’></div>
Элемент, в котором прокручивается контент. Для того, чтобы контент не был виден за его пределами, вьюпорту присвоен стиль overflow: hidden. При этом ещё заперещается показ дефолтной полосы прокрутки.
<div class=’content’></div>
К этому блоку и будет применяться прокрутка.
<div class=’container-footer flex-middle’></div>
Футер блока. Этот элемент необязательный, можно обойтись без него.

Обратите внимание — показ дефолтной полосы прокрутки мы запретили, а HTML-вёрстку кастомного скроллбара не сделали. Он будет формироваться JS-скриптом в случае необходимости, т. е., если высота контента больше высоты вьюпорта. Такое решение предоставляет большую гибкость в применении, отсутствует зависимость от высоты контента. Это удобно, когда на странице несколько таких блоков с разным наполнением контентом.

Таблица стилей для блока с прокруткой.

Ниже я приведу только те стили, которые непосредственно относятся к блоку с прокруткой контента.

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

Начинаем писать JavaScript для блока с прокруткой контента.

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

При написании JS-скрипта мы будем использовать прототипное наследование. Это позволит создать несколько блоков с прокруткой на одной странице.

Для создания объектов блоков с прокруткой контента мы будем использовать конструктор, т. к. он хорошо подходит для создания множества похожих объектов, имеющих одинаковую структуру, с общими именами свойств и методов. В JavaScript это называется наследованием через прототипы, а объект от которого наследуются свойства и методы, называется прототипом. В нашем случае, это объект созданный конструктором. Другими словами, механизм работы наследования через прототипы заключается в наследовании и повторном использовании свойств и методов созданных в конструкторе, с возможностью расширения свойств во вновь созданном объекте.
Конструктор представляет из себя обычную функцию с набором настроек, определяющих структуру блоков с прокруткой, их поведение и способы управления.

Конструктор принимает два аргумента:

  1. container — объект блока с контентом, экземпляр которого создаётся в данный момент;
  2. nameEvent — событие, которое будет использоваться для прокрутки контента.

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

Создание коллекции блоков с прокруткой и её обработка.

Первое, что необходимо сделать — найти на странице все блоки, где будет прокручиваться контент.
Рассматривая HTML-вёрстку, мы выяснили, что у все этих блоков есть атрибут data-control, значение которого определяет событие, управляющее прокруткой. Используя этот атрибут, мы создадим коллекцию нужных нам элементов.

При переборе полученной коллекции элементов, мы выполним следующие действия:

  1. определим значение атрибута data-control текущего блока;
  2. создадим экземпляр текущего блока, используя конструктор;
  3. создадим HTML-вёрстку скроллбара, если высота контента в текущем блоке больше высоты вьюпорта.
  4. зарегистрируем обработчики событий.

Рассмотрим JS-код, реализующий наши действия:

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

Конструктор экземпляров блоков с прокруткой контента.

Как я писал выше, конструктор представляет собою обычную функцию вызванную через new. По соглашению, имя такой функции пишется с заглавной буквы (PascalCase).

Конструктор устанавливает значения ряду свойств, которые будут использоваться при прокрутке контента:

nameEvent
имя события, которое будет использоваться для прокрутки;
viewport
объект элемента, в котором прокручивается контент и расположен скроллбар, другими словами, область просмотра;
content
объект элемента, в котором расположен контент;
viewportHeight
высота вьюпорта;
contentHeight
высота контента;
max
максимальное значение, на которое может быть прокручен контент;
ratio
соотношение между высотами вьюпорта и контента;
scrollerHeightMin
минимальная высота ползунка скроллбара;
step
шаг прокручивания контента при наступлении события ‘wheel, позволяет изменять скорость прокрутки;
pressed
флаг нажатия на левую кнопку мыши.

JS-код конструктора:

Создание скроллбара для блока с прокруткой контента.

При переборе коллекции блоков с прокруткой, вызывается функция scrollbox.init. Эта функция решает следующие задачи:

  1. Сравнивает высоту контента с высотой вьюпорта. Если высота контента окажется меньше или равна высоте вьюпорта, то функция прекращает работу, т. к. необходимость в прокрутке отсутствует.
  2. Вызывает функцию createScrollbar для создания полосу прокрутки.
  3. Вызывает функцию registerEventsHandler регистрирующую обработчики событий для управления прокруткой как вращением колёсика мыши, так и перетаскиванием ползунка скроллбара.

Код функции и комментарии к ней:

Функция createScrollbar делает следующее:

  1. Используя встроенный в JavaScript метод document.createElement, создаёт новые DOM-элементы DIV, из которых будут сформированы полоса прокрутки и ползунок.
  2. Присваивает созданным элементам классы — scrollbar и scroller соответственно.
  3. Вставляет созданные элементы во вьюпорт.
  4. Получает DOM-объект ползунка полосы прокрутки, вычисляет и устанавливает его высоту.
  5. Вычисляет максимально возможное смещение ползунка от верхней границы вьюпорта.

Высота ползунка зависит от соотношения высот вьюпорта и контента (коэффициент ratio), при этом чем больше высота контента, тем меньше высота ползунка полосы прокрутки.

Теперь необходимо зарегистрировать обработчики событий, которые будут управлять прокруткой контента внутри вьюпорта.

Управление прокруткой контента внутри блока.

Регистрация обработчиков событий для блока с прокруткой контента.

Регистрация обработчиков событий осуществляется в функции registerEventsHandler, вызываемой из рассмотренной ранее функции init. Обработчик назначается вызовом метода addEventListener, который привязывается к элементу, на котором событие регистрируется.

Запомните, это очень важно.
При использовании метода addEventListener теряется контекст вызова — this, который ссылается на текущий объект. Используя встроенный в JavaScript метод bind, можно напрямую передать контекст вызова в функцию обработчика события.

Регистрировать мы будем следующие события:

wheel
вращение колёсика мыши, при этом курсор должен быть над вьюпортом;
mousedown
нажатие на левую кнопку мыши, когда курсор находится на ползунке;
mousemove
перемещение мыши по полосе прокрутки и документу в целом, т. е., если при перемещении курсор покинет область полосы прокрутки, перемещение ползунка не прервётся;
mouseup
отпускание левой кнопки мыши, при этом курсор может находиться над любой частью документа.

JS-код функции registerEventsHandler:

Как видно из кода, события wheel и mousedown привязываются к элементам viewport и scroller соответственно. А вот события mousemove и mouseup привязаны к объекту document, т. е. событие может наступить при положении курсора над любой частью страницы. Такое поведение предусмотрено при дефолтной прокрутке контента внутри блока при назначении ему стиля overflow-y: scroll. Логично, что я постарался сохранить его.

Прокрутка контента вращением колёсика мыши.

Обработчиком события wheel является функция scroll. Рассмотрим алгоритм работы данной функции:

  • 1

    Используя свойство события e.deltaY, определяет направление вращения колёсика мыши dir и меняет его знак на противоположный.

  • 2

    Определяет шаг, с которым будет прокручиваться контент, с учётом полученного значения dir и величины e.deltaY, на которую повёрнуто колёсико мыши. Величина шага задана в конструкторе в свойстве this.step.
    У вас может возникнуть вопрос: зачем мы сами устанавливаем размер шага прокрутки, а не используем значение e.deltaY? На это есть несколько причин:

    1. В разных браузерах, при одинаковом повороте колёсика мыши, значения e.deltaY очень сильно отличаются. В результате скорость прокручивания контента будет разной.
    2. Самостоятельно устанавливая значение this.step, мы можем регулировать скорость прокрутки.
  • 3

    Управляет позиционированием контента, меняя его смещение по вертикали с учётом значения content.offsetTop и полученного значения step.

    Обратите внимание, это очень важно.
    Для вычисления текущей прокрутки используется значение content.offsetTop, а не content.scrollTop. Так как у элемента content установлено CSS-свойство position:absolute, то его расположение вычисляется относительно ближайшего позиционированного элемента, которым является вьюпорт.
  • 4

    Ограничивает прокручивание контента на верхней и нижней позиции, т. е. значение content.offsetTop не может быть больше 0 и меньше this.max, значение которого вычислено было ранее в конструкторе.

  • 5

    Перемещает ползунок пропорционально прокрутке контента.

JS-код функции scroll вместе с комментариями:

Далее я расскажу вам, как перетаскивается ползунок по полосе прокрутки и как меняется прокрутка контента в зависимости от текущего положения ползунка.

Прокрутка контента перемещением ползунка скроллбара.

Перетаскивание ползунка начинается с наступления события mousedown. При обработке этого события запоминается координата по оси Ye.clientY, которая будет считаться точкой начала перемещения ползунка. Кроме этого, выставляется флаг сигнализирующий о нажатии левой кнопки мыши.
JS-код, реализующий это, находится в функции, являющейся вторым параметром регистратора обработчика события mousedown. Напомню этот код, чтобы вам не листать страницу:

За перетаскивание ползунка отвечает функция drop, которая вызывается при возникновении события mousemove. Давайте рассмотрим алгоритм работы этой функции:

  • 1

    Проверяет установку флага нажатия левой кнопки мыши pressed. Если флаг не установлен, то прекращает свою работу.

  • 2

    Вычисляет величину перемещения мыши, как разницу между координатой старта this.start и текущей координатой курсора по оси Ye.clientY.

  • 3

    Изменяет положение бегунка на величину перемещения курсора, меняя его смещение по вертикали путём изменения CSS-свойства top.

  • 4

    Ограничивает перемещение ползунка верхней и нижней границей вьюпорта.

  • 5

    Прокручивает контент на величину пропорциональную перемещению ползунка.

  • 6

    Устанавливает координату Y начала движения мыши равной текущей координате Y.

Прежде чем начнём писать код функции drop, давайте подробнее разберём п. 4 представленного алгоритма.

Ранее нами было получено максимально возможное смещение ползунка от верхней границы вьюпорта — scrollerMaxOffset, вычисленное как разность высоты вьюпорта и высоты ползунка. Давайте посмотрим на рисунок:

Прокрутка блока. Максимально смещение ползунка.

Из рисунка видно, что если попытаться ещё увеличить смещение ползунка, то его нижняя часть выйдет за пределы вьюпорта. Другими словами, сумма высоты ползунка и его смещения не должна превышать высоту вьюпорта.

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

Теперь можно представить полный код функции drop:

При отпускании левой кнопки мыши, наступает событие mouseup и перетаскивание ползунка скроллбара прекращается. При этом флаг pressed получает значение false.

Напомню этот код, чтобы вам не листать страницу:

Итак, мы рассмотрели, как прокручивать контент в блоке перетаскиванием ползунка или используя событие wheel, возникающее при прокручивании колёсика мыши. Теперь настало время рассмотреть прокручивание контента по событию scroll.

Особенности управления прокруткой контента внутри блока по событию ‘scroll’.

Скопируем вёрстку созданного нами блока с прокруткой контента и внесём в неё пару дополнений и изменений:

  1. Изменим у родительского блока значение атрибута data-control на scroll.

    Это понадобится для изменения алгоритма работы JS-скрипта в зависимости от имени события, используемого для прокручивания контента.

  2. Добавим блоку <div class='content'></div> свойство overflow-y: auto. Когда текст внутри блока перестанет вмещаться по высоте, автоматически появится стандартная полоса прокрутки шириной 17px. Для того, чтобы она стала невидимой, растянем блок content вправо, за пределы вьюпорта на 17px. Вьюпорт имеет свойство overflow: hidden и, соответственно, всё что выступает за его пределы, станет невидимым.

    Добавим блоку content класс content-scroll:

  3. Пропишем новый класс в таблице стилей:

При возникновении события scroll, нам не понадобится вызывать функцию scroll, т. к. прокрутка будет осуществлятся стандартными методами встроенными в браузер. Нам потребуется только изменить положение ползунка.

Внесём изменение в регистратор обработчиков событий, добавив условие проверки имени события прокрутки:

На этом изучение прокрутки контента в блоке можно считать законченным. Посмотреть пример различных вариантов прокрутки блока, скачать HTML-вёрстку и полный JS-код, Вы можете по ссылкам, указанным в начале страницы.

Комментарии

Всего: 6 комментариев
Требования при посте комментариев:
  1. Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
    Комментарии содержащие обсуждение политики, будут безжалостно удаляться.
  2. Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега <pre>:
    <pre class="lang:xhtml"> - HTML;
    <pre class="lang:css"> - CSS;
    <pre class="lang:javascript"> - JavaScript.
  3. Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.
  • А есть такое для горизонтального скролла?

    • Отдельно такой скрипт не писал, т. к. текущий легко, в основном, за счёт вёрстки и стилей, преобразуется в горизонтальный скролл блока. Хочу обратить особое внимание, что в этом случае нужно использовать событие мыши «wheel».

  • Можно архив не rar пожалуйста)

  • Спасибо автору за прекрасный урок.

    Немного доработал код для поддержки события resize, но не уверен в правильности кода. Пока осваиваю премудрости js. Прошу вас указать на недостатки и, если возможно, предложить более продвинутый вариант. Из замеченных недостатков — скролл не удаляется, когда в нем нет необходимости. Помогите пожалуйста поправить.

    В секцию fn.registerEventsHandler = function(e) {…
    // document resize (добавил в самом низу)
    // возможно ли отказаться от window, заменив его на document?
    window.addEventListener(‘resize’, () => {
    this.refreshScrollbar();
    });
    };

    ниже функции
    fn.createScrollbar = function() {

    };
    добавил функцию
    fn.refreshScrollbar = function() {
    this.content = this.viewport.querySelector(‘.content’);
    this.viewportHeight = this.viewport.offsetHeight;
    this.contentHeight = this.content.scrollHeight;
    // возможная максимальная прокрутка контента
    this.max = this.viewport.clientHeight — this.contentHeight;
    // соотношение между высотами вьюпорта и контента
    this.ratio = this.viewportHeight / this.contentHeight;
    // устанавливаем его высоту
    this.scroller = this.viewport.querySelector(‘.scroller’);
    this.scrollerHeight = parseInt(this.ratio * this.viewportHeight);
    this.scrollerHeight = (this.scrollerHeight < this.scrollerHeightMin) ? this.scrollerHeightMin : this.scrollerHeight;
    this.scroller.style.height = this.scrollerHeight + 'px';
    // вычисляем максимально возможное смещение ползунка от верхней границы вьюпорта
    // это смещение зависит от высоты вьюпорта и высоты самого ползунка
    this.scrollerMaxOffset = this.viewportHeight — this.scroller.offsetHeight;
    };

  • Также замечен недостаток, что при resize прячется индикатор прокрутки, если конента много и ранее была прокрутка вниз.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *