Плавная прокрутка страницы на JavaScript

Вступление.

Во многих продающих лендингах используется плавная прокрутка страницы к определённому тематическому блоку, поэтому тема плавной прокрутки неоднократно рассматривалась во многих статьях, размещённых на сайтах или блогах посвящённых программированию на JavaScript. Предлагались разные варианты решения, как на чистом JavaScript, так и с использованием jQuery. Большинство из них использует anchor (якоря) и ссылки указывающие на них, что вызывает много споров в комментариях о целесообразности такого метода с точки зрения СЕО. Есть варианты, в которых связь между элементом навигации и блоком, к которому нужно прикрутить страницу, осуществляется через ID. Этот способ более интересный, но и он не лишён недостатков.

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

HTML-разметка для плавной прокрутки страницы.

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

За основу меню взят немаркированный список <ul>, а элементами управления будут <span>. Использование <span> в качестве интерактивного элемента устраняет ещё одну довольно распространённую ошибку — использование для этих целей тэга <a> (ссылки).

Запомните, это очень важно.
Как с точки зрения семантики, так и с точки зрения СЕО — тэг <a> должен использоваться только для формирования ссылок, ведущих на другие страницы сайта или другой интернет-ресурс. Для управления элементами текущей страницы (показать / скрыть, изменить стиль, переместить, подгрузить и т.д.) должны использоваться элементы <span>, <button>, <div>, <li>. Именно на них вешаются обработчики событий.

Исходный код разметки HTML:

Как видно из HTML-вёрстки, я не использовал ни якоря, ни ссылки на них, ни идентификаторы.

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

Небольшая таблица стилей, которая определяет внешний вид шапки, меню и самих контейнеров с контентом:

JavaScript для управления плавным прокручиванием страницы.

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

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

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

Теперь необходимо получить ряд объектов и коллекций элементов на основе которых и будет работать наш JavaScrit:

  1. объект menu — для делегирования, основанного на всплытии событий;
  2. коллекция объектов SPAN — интерактивные элементы, запускающие прокручивание страницы;
  3. коллекция объектов DIV — контейнеры, до которых прокручивается страница.

Теперь нам нужно повесить обработчик события click на объект menu. При срабатывании события (по меню был сделан клик) и используя делегирование, основанное на всплытии событий, определяем, что клик произошел именно по элементу span, а не по пустой области меню. После этого запускаем по очереди две функции: switchLinks и selectContainer.

Рассмотрим функцию switchLinks, отвечающую за подсветку элемента меню, по которому сделан клик. Кроме этого, она возвращает index (current) данного элемента в коллекции items. Этот индекс понадобиться для идентификации контейнера, до которого будет прокручиваться страница.
Аргументом функции является объект элемента span, по которому был сделан клик. Кроме полученного аргумента, функция switchLinks использует коллекцию объектов items. Данная коллекция не передаётся в функцию явно в качестве аргумента, т. к. она является глобальной в пределах области видимости самозапускающейся анонимной функции.

Теперь разберём JS-код функции selectContainer. Данная функция находит контейнер из коллекции containers, используя аргумент current — индекс активного элемента меню и запускает анимированное прокручивание страницы, вызывая функцию scroll.

Осталось рассмотреть работу основной функции нашего скрипта — scroll, которая обеспечивает плавную прокрутку страницы до выбранного контейнера, учитывая направление прокрутки.

Функция принимает два аргумента:
1. container — объект элемента DIV до которого будет прокручиваться страница;
2. direction — коэффициент, определяющий вверх или вниз необходимо прокручивать страницу.

Теперь подробно, по шагам разберёмся, как будет работать анимация прокрутки страницы.

  • 1

    Перед запуском анимации необходимо назначить несколько переменных и присвоить им значения:
    duration, длительность анимации;
    start, время начала анимации;

  • 2

    Внутри функции scroll создадим именованную функцию fn, которая будет рекурсивно вызывать себя до тех пор, пока не закончится анимация. Внутри этой функции объявим и присвоим значения ещё трём переменным:
    top, текущее положение верхней границы контейнера с учётом высоты шапки с меню;
    now, время прошедшее от начала анимации прокрутки страницы;
    result, величина прокрутки страницы за один цикл функции fn.

    где, 96 — высота шапки.

  • 3

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

  • 4

    Сравниваем произведение direction * top c 0. Если произведение больше нуля, то прокручиваем страницу на величину result и рекурсивно запускаем функцию fn на очередной цикл.

    При прокручивании страницы вниз значения direction и top — положительные, при прокручивании вверх — отрицательные, поэтому результат умножения всегда положительный.

    Метод scrollBy(x,y) прокручивает страницу относительно текущих координат, соответственно window.scrollBy(0,result) прокрутит страницу по координате ‘Y’ на величину result.

  • 5

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

Теперь соберём всё вместе и посмотрим, как выглядит функция scroll полностью:

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

всегда будет истинно и наш скрипт зациклится.

Давайте внимательно рассмотрим рисунок:

Плавная прокрутка страницы

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

Чтобы реализовать эту зависимость в качестве условия прекращения работы скрипта, необходимо получить следующие значения:

  1. Высота документа (страницы).
    Определить размер страницы с учетом прокрутки можно, взяв максимум из нескольких свойств:

    Добавим этот код в начале нашей анонимной функции, после строки:

  2. Текущая прокрутка страницы
    Текущую прокрутку можно получить, используя специальное свойство window.pageYOffset

  3. Высота видимой части окна
    Высота видимой части окна содержится в свойстве document.documentElement.clientHeigh

Обновим функцию scroll с учётом выше написанного:

Итак, скрипт плавной прокрутки страницы работает, кажется, все нюансы учтены и на этом можно остановиться.

Комментарии

Всего: 4 комментария
Требования при посте комментариев:
  1. Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
    Комментарии содержащие обсуждение политики, будут безжалостно удаляться.
  2. Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега <pre>:
    <pre class="lang:xhtml"> - HTML;
    <pre class="lang:css"> - CSS;
    <pre class="lang:javascript"> - JavaScript.
  3. Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.
  • Привет
    А чем scrollIntoView не нравится?
    element.scrollIntoView({behavior: «smooth»});
    https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

    • Вальдемар Леонид Ответить 20 июня 2019 в 17:06

      Нельзя регулировать длительность анимации, да и не всеми, даже современными, браузерами поддерживается.
      А так, конечно, если устраивает «smooth», то лучше использовать scrollIntoView — значительно сократится и упростится js-код.

  • Спасибо! выручили!

  • Почему то, если покликать быстро на разные ссылки подряд, она зависает и замирает в конце. Даже на вашем демо.
    Скролл колесом мышки становится нерабочей, да и по полосе она замирает.
    Попробуйте сами.

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

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