Перевод статьи на webdeasy.de
При таком скроллинге на весь экран нормальная прокрутка отключается, и вы всегда переходите точно на следующую секцию. Ниже, с помощью Vue.js, вы самостоятельно напишите код для подобной прокрутки.
Оглавление
Фреймворки для прокрутки на всю страницу (Fullpage Scroll)
Демо
Чтобы с самого начала было понятно, как будет выглядеть результат, ниже я подготовил демонстрацию. Эта версия поддерживается всеми распространенными браузерами, а также работает на мобильных устройствах.
Однако в браузере Microsoft Edge (преемнике IE) есть небольшая проблема: чистая плавная прокрутка (Smooth Scroll) здесь не поддерживается. Прокрутка немного прерывистая, но все функции по-прежнему можно использовать. Поэтому мы нашли альтернативное решение.
Если вы все еще в поиске дизайнерского вдохновления, у меня есть еще одна страница, на которой, на мой взгляд, очень хорошо настроен скроллинг:
Фреймворки для прокрутки на всю страницу (Fullpage Scroll)
Я знаю, что существует уже несколько действительно хороших фреймворков для прокрутки в Vue.js, например такой как здесь. Однако гораздо круче написать код самостоятельно. Тем более у вас будет меньше «ненужных» функций на сайте, которые вы не используете.
Если вы не дружите с Vue.js, вы можете использовать то же самое в React , AngularJS или jQuery . Процедура в основном похожа. Некоторые функции, конечно, придется адаптировать к фреймворку.
Структура HTML
Для начала нам нужен контейнер, в котором будет работать наше Vue.js приложение. Поэтому создадим контейнер #app
. Внутри будет вся остальная разметка HTML.
1 2 3 |
<div id="app"> <!-- CONTENT HERE --> </div> |
Теперь создадим наши секции. Внутри каждой секции мы можем разместить и стилизовать любой наш контент.
1 2 3 4 5 6 7 8 9 10 11 12 |
<section class="fullpage"> <h1>Section 1</h1> </section> <section class="fullpage"> <h1>Section 2</h1> </section> <section class="fullpage"> <h1>Section 3</h1> </section> <section class="fullpage"> <h1>Section 4</h1> </section> |
Следующим шагом создадим боковое меню. Которое позволяет перейти к другой секции, после щелчка на которой и отобразится наша секция.
1 2 3 4 5 6 7 8 9 |
<div class="sections-menu"> <span class="menu-point" v-bind:class="{active: activeSection == index}" v-on:click="scrollToSection(index)" v-for="(offset, index) in offsets" v-bind:key="index"> </span> </div> |
Объясню код выше: для создания меню мы создали обертку (wrapper) .sections-menu
. В нем есть тег span
, который содержит некоторые директивы для Vue.js. Ниже описаны данные директивы:
Атрибуты | Значения | Описание |
v-bind:class
|
{active: activeSection == index} | Тег возвращает класс в active , если текущая итерация цикла равна активной секции. |
v-on:click
|
scrollToSection (index) | Нажатие на ссылку вызывает функцию scrollToSection() . В качестве параметра передается ID секции. |
v-for
|
(offset, index) in offset | Число элементов меню равно количеству элементов в массиве offset . Значения index равны идентификаторам секций. |
v-bind:key
|
index | Чтобы сохранить каждый проход цикла уникальным, мы устанавливаем наш идентификатор раздела в качестве ключа для директивы for . |
CSS
Чтобы эффект полной прокрутки экрана был как таковым, каждая секция должна быть не менее 100vh (то есть в высоту экрана). Таким образом, вы должны следить, чтобы контент в секции был ориентирован на эту высоту.
1 2 3 4 |
.fullpage { height: 100vh; width: 100%; } |
В нашем примере у нас есть только заголовок и подзаголовок. Для наглядности взгляните на демо выше.
Навигация
Я оформил навигационное меню как можно проще. Белые точки, где активная точка жирнее и крупнее. Кроме того, меню всегда находится на правой стороне экрана. CSS выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
.sections-menu { position: fixed; right: 1rem; top: 50%; transform: translateY(-50%); } .sections-menu .menu-point { width: 10px; height: 10px; background-color: #FFF; display: block; margin: 1rem 0; opacity: .6; transition: .4s ease all; } .sections-menu .menu-point.active { opacity: 1; transform: scale(1.5); } |
Стилизацию и позиционирование текста и тому подобное я здесь убрал, потому что это к теме не относится. Полный код вы можете найти на моем pen.
Vue.js (JavaScript)
JavaScript здесь оказался самой сложной частью. Потому что нам нужно охватить все современные браузеры, а также мобильные устройства.
Инициализация Vue.js приложения
Сперва инициализируем приложение Vue.js. Мы уже создали контейнер #app
в HTML.
1 2 3 |
var app = new Vue({ el: '#app', }); |
Объявление и инициализация переменных
Теперь объявим и инициализируем переменные. Позже будет понятно, какая переменная для чего нам нужна, если ее имя нам об этом ничего не говорит.
1 2 3 4 5 6 |
data: { inMove: false, activeSection: 0, offsets: [], touchStartY: 0 } |
Рассчитываем смещения
Теперь нам нужно вычислить смещения (верхний край секций) отдельных секций. Преимущество заключается в том, что нам не нужно пересчитывать смещение каждый раз при прокрутке.
Мы запускаем цикл над всеми элементами section
и сохраняем смещение в нашем глобальном массиве offsets
.
1 2 3 4 5 6 7 8 |
calculateSectionOffsets() { let sections = document.getElementsByTagName('section'); let length = sections.length; for (let i = 0; i < length; i++) { let sectionOffset = sections[i].offsetTop; this.offsets.push(sectionOffset); } } |
Функция calculateSectionOffsets()
вызывается один раз при создании приложения, в функции created()
.
1 2 3 |
created() { this.calculateSectionOffsets(); } |
Слушатель событий (Event Listener)
Нам нужны некоторые слушатели событий, для перехвата скроллинга на десктопах и для перехвата свайпов на мобильных устройствах. Регистрируем эти слушатели в функции created()
.
1 2 3 4 5 6 7 8 9 10 11 12 |
window.addEventListener('wheel', this.handleMouseWheelDOM, { passive: false }); // Mozilla Firefox window.addEventListener('mousewheel', this.handleMouseWheel, { passive: false }); // Other browsers window.addEventListener('touchstart', this.touchStart, { passive: false }); // mobile devices window.addEventListener('touchmove', this.touchMove, { passive: false }); // mobile devices |
Также в функции destroy()
удаляем слушатели событий при выходе из приложения.
1 2 3 4 5 6 7 8 9 10 |
destroyed() { window.removeEventListener('mousewheel', this.handleMouseWheel, { passive: false }); // Other browsers window.removeEventListener('wheel', this.handleMouseWheelDOM, { passive: false }); // Mozilla Firefox window.removeEventListener('touchstart', this.touchStart); // mobile devices window.removeEventListener('touchmove', this.touchMove); // mobile devices } |
Как видно из комментариев, для разных браузеров существуют разные события. Для некоторых браузеров задан параметр passive: false
. Этот параметр должен быть обязательным, иначе прокрутка не будет плавной, а также будут выводиться ошибки в консоли.
Функция прокрутки
Данная функция вызывается нашими HTML-ссылками. По которым мы и переходим к секциям. Идентификатор id
это идентификатор секции. Значение inMove
обеспечивает нам небольшую задержку. Это значит, что мы можем прокручивать только каждые 400 мс (0,4 с). Если параметр force
установить в true
то задержку мы пропустим.
1 2 3 4 5 6 7 8 9 10 11 |
scrollToSection(id, force = false) { if (this.inMove && !force) return false; this.activeSection = id; this.inMove = true; document.getElementsByTagName('section')[id].scrollIntoView({ behavior: 'smooth' }); setTimeout(() => { this.inMove = false; }, 400); }, |
Распознавание направления прокрутки
Функция handleMouseWheel
является слушателем события прокрутки на десктопах (события mousewheel
& DOMMouseScroll
).
Через значение wheelDelta
события мы можем определить, прокручивает пользователь вверх или вниз. Соответственно, следующим шагом мы выполняем функцииmoveUp()
или moveDown()
. В конце e.preventDefault()
и return false;
завершают событие.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
handleMouseWheelDOM: function(e) { if (e.deltaY > 0 && !this.inMove) { this.moveUp(); } else if (e.deltaY < 0 && !this.inMove) { this.moveDown(); } e.preventDefault(); return false; }, handleMouseWheel: function(e) { if (e.wheelDelta < 30 && !this.inMove) { this.moveUp(); } else if (e.wheelDelta > 30 && !this.inMove) { this.moveDown(); } e.preventDefault(); return false; } |
Прокручивание секции вверх и вниз
Следующие две функции при вызове один раз выполнят прокрутку вверх или вниз. Если мы прокручиваем вниз, а номер секции меньше 0, мы переходим к последней секции. И наоборот, если новая секция больше, чем количество секций, мы переходим к первой секции. 0 это первая секция.
1 2 3 4 5 6 7 8 9 10 11 12 |
moveDown() { this.inMove = true; this.activeSection--; if (this.activeSection < 0) this.activeSection = this.offsets.length - 1; this.scrollToSection(this.activeSection, true); }, moveUp() { this.inMove = true; this.activeSection++; if (this.activeSection > this.offsets.length - 1) this.activeSection = 0; this.scrollToSection(this.activeSection, true); } |
В функциях мы написали бесконечный скроллинг. Если это не написать, то мы просто не сможем выполнить Swipe в крайних секциях, только если в обратном направлении.
Определение мобильных свайпов
Для этого у нас есть события движения touchstart
и touchmove
, через которые мы можем контролировать прокрутку пользователя. Когда пользователь начинает прокручивать происходит вызов события touchStart()
. В ней мы сохраняем значение позиции Y. Когда пользователь затем перемещает палец по дисплею вызывается событие touchMove()
.
Затем мы сравниваем эти два значения и видим, прокручивает ли пользователь вверх или вниз. Соответственно, вызываются функции moveUp()
или moveDown()
, которые мы создали ранее.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
touchStart(e) { e.preventDefault(); this.touchStartY = e.touches[0].clientY; }, touchMove(e) { if (this.inMove) return false; e.preventDefault(); const currentY = e.touches[0].clientY; if (this.touchStartY < currentY) { this.moveDown(); } else { this.moveUp(); } this.touchStartY = 0; return false; } |
Заключение
Как видите, вы можете добавить свои собственные функции или удалить некоторые детали, которые вам не нравятся. Полноэкранная прокрутка — очень шикарная функция, и при правильном использовании можно получить очень хороший результат. Некоторые примеры вы уже видели демо .
Полный код вы найдете в моем pen на Codepen.
Если у вас есть предложения по улучшению или правке кода, пожалуйста, не стесняйтесь писать их в комментариях! 🙂