Наверняка вы посещали страницы, которые плавно прокручивают контент при клике на ссылке к соответствующему блоку. Это красиво выглядит на лэндингах (LandingPage, или посадочная страница), в которых пространство страницы разбито на части, или в больших статьях с содержанием. Такая прокрутка называется скроллингом (от англ. scroll).
Однако, это не только красиво, но и достаточно просто с точки зрения реализации.
Прокрутка страницы с помощью CSS
Для того чтобы плавная прокрутка происходила на всей странице, необходимо добавить свойство scroll-behavior: smooth
для селектора html
.
1 2 3 | html { scroll-behavior: smooth; } |
Если плавная прокрутка необходима в пределах какого-то контейнера, то это свойство назначают для него.
По умолчанию свойство scroll-behavior
имеет значение auto
, т.е. прокрутка будет обычной, без эффекта плавности.
Посмотрите пример, основанный на css-свойстве (открыть в новой вкладке):
Примечание: в каждом примере есть 5 ссылок вверху для прокрутки к блокам текста и ссылка со стрелкой в правом нижнем углу для возврата наверх страницы. Используйте их для тестов свойств и методов для плавного скроллинга страницы.
Ложкой дегтя для этого свойства будет неполная поддержка его браузерами. Вы можете посмотреть, какова она на скриншоте и на caniuse.com.
Поэтому рассмотрим, как сделать плавную прокрутку с помощью jQuery и JavaScript.
Скроллинг с помощью jQuery
Понятно, что для использования jQuery вам нужно будет сначала подключить эту библиотеку. Дальше нужно будет отслеживать клики по ссылкам, у которых есть хэш (#
) и анимировать свойство scrollTop
для селектора $('html, body')
.
Сам код будет небольшим:
1 2 3 4 5 6 7 8 9 10 11 12 | $(document).ready(function(){ $('[href^="#"]').on('click', function(event){ if ($(this).attr('hash') !== "") { event.preventDefault(); let hash = $(this).prop('hash'); $('html, body').animate({ scrollTop: $(hash).offset().top }, 800, function(){ }); } }); }); |
Это решение является кроссбраузерным, хотя у него есть один недостаток - если на вашем сайте jQuery не используется для работы с другими объектами/плагинами, то подключать лишние 88кб или порядка 40кб в gzip-сжатом виде не очень интересно ради 10-15 строк кода.
Пример (открыть в новой вкладке):
Плавная прокрутка на JavaScript
Здесь тоже есть 3 решения, каждое из которых использует свой подход к созданию плавности прокрутки с помощью разных JS-методов.
Решение 1. Метод scrollIntoView()
Из документации на MDN узнаем, что
Метод
Element.scrollIntoView()
прокручивает текущий контейнер родителя элемента, так, чтобы этот элемент, на котором был вызванscrollIntoView()
был видим пользователю.
Этот метод имеет параметры, подобные css-свойству scroll-behavior: smooth
для прокрутки контента к элементу с нужным id, указанным в виде хэш в ссылке.
1 2 3 4 5 6 7 8 9 10 11 | const links = document.querySelectorAll('a[href^="#"]');// все ссылки, с атрибутом href, начинающимся с "#" links.forEach(item => item.addEventListener('click', function(e) { e.preventDefault(); const id = item.getAttribute('href').slice(1); document.getElementById(id).scrollIntoView({ behavior: 'smooth', block: 'start' }); })); |
К сожалению, и тут не обошлось без "ложки дегтя" в виде поддержки браузеров. Давайте обратимся к caniuse.com и увидим такую картину:
К сожалению, нужное нам значение свойства behavior: 'smooth'
поддерживается не всеми браузерами.
Кроме того, если верхняя панель навигации у нас зафиксирована, т.е. имеет свойство position: fixed
, то нужно будет добавить к прокрутке смещение на ее высоту.
Пример прокрутки контента с помощью метода scrollIntoView()
(открыть в новой вкладке)).
Решение 2. Используем window.scrollBy()
для плавной прокрутки.
Тут все методы и свойства и имеют хорошую поддержку браузерами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | let links = document.querySelectorAll('a[href^="#"]'), // все ссылки, с атрибутом href, начинающимся с "#" pos = document.headerPosition.pos,// все radio-переключатели header = document.querySelector('header'), //элемент header, который может быть спозиционирован абслютно или фиксированно topOffset = 0;// отступа сверху нет for (let one of pos) { one.addEventListener('click', function() { header.style.position = this.value; if (this.value !== 'static') { header.classList.add(this.value); document.body.classList.add('for-fixed'); topOffset = header.offsetHeight; } else { header.removeAttribute('class'); document.body.classList.remove('for-fixed'); topOffset = 0; } }) } links.forEach(item => { item.addEventListener('click', function(e) { e.preventDefault(); let href = this.getAttribute('href').slice(1); const targetElem = document.getElementById(href); const elemPosition = targetElem.getBoundingClientRect().top; const offsetPosition = elemPosition - topOffset; window.scrollBy({ top: offsetPosition, behavior: 'smooth' }); }); }); |
Код JavaScript предполагает, что на вашей странице нет абсолютно позиционированной или фиксированной шапки сайта (элемент <header>
), в котором чаще всего размещаются ссылки-якоря на разделы страницы, поэтому переменная offsetTop
(смещение сверху) сначала задана как 0.
Если же шапка сайта, например фиксирована (для этого в примере есть переключатель), то отступ сверху нужен и для <body>
(задается в классе .for-fixed
), и для всех тех блоков, на которые указывают ссылки. Поэтому в переменную offsetTop
мы записываем расчетную высоту <header>
.
Пример прокрутки контента с помощью window.scrollBy()
(открыть в новой вкладке)).
Решение 3. Использование методов requestAnimationFrame() и window.scrollTo() для плавной прокрутки
Метод window.requestAnimationFrame() позволяет выполнить анимацию, используя в качестве параметра функцию, которая будет вызвана перед перерисовкой экрана в браузере. В примере функция имеет имя step
и плавно прокручивает окно браузера к соответствующему блоку с помощью метода window.scrollTo()
в зависимости от параметра velocity
, который задан, как .8
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | let links = document.querySelectorAll('a[href^="#"]'), velocity = .8; // скорость, причем чем меньше значение - тем больше скорость pos = document.headerPosition.pos, header = document.querySelector('header'), topOffset = 0; // отступа сверху нет for (let one of pos) { one.addEventListener('click', function() { header.style.position = this.value; if (this.value !== 'static') { header.classList.add(this.value); document.body.classList.add('for-fixed'); topOffset = header.offsetHeight; } else { header.removeAttribute('class'); document.body.classList.remove('for-fixed'); topOffset = 0; } }) } for (let linkNav of links) { linkNav.addEventListener('click', function(e) { //по клику на ссылку e.preventDefault(); //отменяем стандартное поведение let winYOffset = window.pageYOffset, // производим прокрутка прокрутка hash = this.href.replace(/[^#]*(.*)/, '$1'); // к id элемента, к которому нужно перейти elemTop = document.querySelector(hash).getBoundingClientRect().top-topOffset, // отступ от окна браузера до id с учетом смещения при header с position: absolute или fixed start = null; requestAnimationFrame(step); // покадровая перерисовка анимации function step(time) { if (start === null) start = time; let progress = time - start, r = (elemTop < 0 ? Math.max(winYOffset - progress / velocity, winYOffset + elemTop) : Math.min(winYOffset + progress / velocity, winYOffset + elemTop)); window.scrollTo(0, r); if (r != winYOffset + elemTop) { requestAnimationFrame(step) } else return; } }); } |
Посмотрите пример (открыть в новой вкладке):
Бонус
Для тех, кто дочитал статью до конца - бонус в виде различных вариантов форматирования кнопки для скролла страницы вниз от Naoya. Хорошо подходит для шапки страницы.
See the Pen Demo: CSS scroll down button by Elen (@ambassador) on CodePen.