10. scroll-top.js — логика кнопки «Наверх»
Последний файл скриптов. Маленький — меньше двадцати строк, но с несколькими важными решениями: где и как отслеживать прокрутку, почему именно 300 пикселей, и как плавно вернуться наверх.
Зачем отдельный файл
Логика кнопки «Наверх» могла бы жить в script.js, как и модули бургера и активной ссылки. Но мы вынесли её в отдельный файл. Тому есть три причины.
- Изоляция. Кнопка «Наверх» — независимый мини-компонент. Если однажды от неё решат отказаться, достаточно удалить один файл и одну строку подключения.
- Симметрия с CSS. У нас уже есть
scroll-top.css. Стили и логика в парных файлах —scroll-top.css+scroll-top.js— легко находятся вместе. - Порядок загрузки. Кнопка «Наверх» — самая «неважная» функция сайта. Подключаем
scroll-top.jsпоследним, чтобы он не задерживал работу бургера и активной ссылки.
Порядок скриптов в проекте: script.js (бургер, активная ссылка) → scroll-top.js (кнопка «Наверх»). Первое — обязательное для навигации. Второе — приятное дополнение. Если у пользователя медленный интернет, он сначала получит рабочее меню, потом — кнопку «Наверх».
Полный файл
scroll-top.jsdocument.addEventListener('DOMContentLoaded', function () {
const btn = document.getElementById('scrollToTopBtn');
if (!btn) return;
window.addEventListener('scroll', function () {
if (window.pageYOffset > 300) {
btn.classList.add('show');
} else {
btn.classList.remove('show');
}
});
btn.addEventListener('click', function (e) {
e.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
});
Четыре смысловых блока. Разбираем по порядку.
Блок 1: общий каркас
JavaScriptdocument.addEventListener('DOMContentLoaded', function () {
// ...
});
Такой же каркас, как в script.js: дожидаемся готовности DOM, потом запускаем код. Разберём подробности.
document — объект страницы. Через него подписываемся на события.
addEventListener('DOMContentLoaded', …) — подписка на событие «DOM построен». Сработает, когда браузер прочитал HTML и создал все элементы. Картинки, шрифты, стили могут быть ещё не загружены — это нормально, они для нашей логики не нужны.
Зачем дожидаться? Если запустить код раньше, он попытается найти кнопку #scrollToTopBtn — а её ещё нет в DOM. querySelector вернёт null, дальше ничего не произойдёт. Ждём, чтобы кнопка уже была в дереве.
Блок 2: поиск кнопки
JavaScriptconst btn = document.getElementById('scrollToTopBtn');
if (!btn) return;
document.getElementById('scrollToTopBtn') — поиск по идентификатору. В HTML кнопка выглядит так:
<button id="scrollToTopBtn" class="scroll-top-btn" aria-label="Наверх">
Обратите внимание: id="scrollToTopBtn" — это идентификатор. Он должен быть единственным на всей странице. Именно поэтому для кнопки «Наверх» используется getElementById, а не querySelector: метод подчёркивает, что элемент уникален.
id — уникальный идентификатор. Один элемент на странице. Ищется через getElementById('#id') или querySelector('#id').
class — метка для группы элементов. Может быть много. Ищется через querySelector('.class') (первый) или querySelectorAll('.class') (все).
У кнопки «Наверх» есть и id (для JS), и класс .scroll-top-btn (для CSS). Такое разделение — стандартная практика.
if (!btn) return; — защита от отсутствия кнопки. Если getElementById вернул null — выходим из функции. Кнопки нет на этой странице, значит, и логика не нужна.
Разберём синтаксис: ! — оператор «не». !btn означает «если btn ложно». null — ложное значение, поэтому условие сработает. Читается как: «если кнопки нет — выйти».
Блок 3: отслеживание прокрутки
JavaScriptwindow.addEventListener('scroll', function () {
if (window.pageYOffset > 300) {
btn.classList.add('show');
} else {
btn.classList.remove('show');
}
});
Сердце модуля. Отслеживаем прокрутку страницы и переключаем кнопку между «скрыто» и «показано».
Слушаем прокрутку окна
JavaScriptwindow.addEventListener('scroll', function () {
window — глобальный объект окна браузера. Не document (страница), а именно окно. Прокручивается всё окно, а не отдельный элемент. Поэтому подписываемся на window.
Событие 'scroll' срабатывает при каждой прокрутке. Пользователь крутит колесо, тапает по экрану, двигает скроллбар — событие повторяется десятки раз в секунду. Наша функция выполнится столько же раз.
Событие scroll может срабатывать очень часто. Если внутри обработчика делать что-то тяжёлое (например, перебирать сотни DOM-элементов) — страница начнёт «тормозить». Мы делаем простую проверку и меняем один класс — это дёшево. Но правило на будущее: чем меньше работы в обработчике scroll, тем лучше.
Проверка величины прокрутки
JavaScriptif (window.pageYOffset > 300) {
window.pageYOffset — сколько пикселей пользователь прокрутил по вертикали. На верхней точке страницы значение 0. Чем ниже — тем больше.
Почему именно 300? Эмпирическое значение. Если показывать кнопку слишком рано (например, на 50 пикселях) — она выскакивает при лёгком движении, раздражает. Если слишком поздно (на 1000) — пользователь уже устал крутить, а подсказки нет. 300 пикселей — примерно один экран мобильного — золотая середина.
Переключение класса
JavaScriptbtn.classList.add('show');
// …
btn.classList.remove('show');
Два действия в зависимости от порога:
- Прокрутили больше 300 —
classList.add('show')добавляет кнопке классshow. - Вернулись наверх —
classList.remove('show')убирает класс.
А CSS (файл scroll-top.css) в ответ на класс show меняет три свойства: opacity: 1, visibility: visible, transform: translateY(0). Кнопка плавно «выглядывает» снизу.
В модуле бургера мы использовали classList.toggle('active') — «переключи». Здесь же используем add и remove раздельно. Причина: условие сложнее. При каждом событии scroll мы точно знаем, должно ли быть состояние «показано». toggle бы переключал класс при каждом событии — а событий десятки в секунду. Кнопка бы мигала. add и remove — идемпотентны: если класс уже есть, add ничего не сделает. Стабильное поведение.
Блок 4: обработка клика
JavaScriptbtn.addEventListener('click', function (e) {
e.preventDefault();
window.scrollTo({ top: 0, behavior: 'smooth' });
});
Пользователь кликнул по кнопке — плавно возвращаемся к началу страницы.
Отменяем стандартное поведение
JavaScripte.preventDefault();
e — объект события. Мы получаем его как параметр функции. Он содержит все детали произошедшего: что было, где, когда.
e.preventDefault() — «отмени стандартное поведение». Но у <button> стандартного поведения нет. Так зачем вызывать preventDefault?
Это страховка на будущее. Если однажды кнопку превратят в ссылку <a href="#"> — браузер попытается перейти по адресу «#», добавит решётку в URL и «прыгнет» наверх. preventDefault предотвращает это заранее. Код готов к изменениям.
Представим кнопку как <a href="#">↑</a>. Пользователь кликает. Без preventDefault: браузер добавляет # в адресную строку, страница «прыгает» наверх моментально — без плавности, которую мы хотели. И в истории появляется лишняя запись. С preventDefault — ничего этого не происходит.
Плавная прокрутка к началу
JavaScriptwindow.scrollTo({ top: 0, behavior: 'smooth' });
window.scrollTo() — «прокрути окно к указанным координатам». У метода две формы вызова:
- Старая:
window.scrollTo(0, 0);— «иди к координатам (x=0, y=0) мгновенно». - Новая:
window.scrollTo({ top: 0, behavior: 'smooth' });— «иди к точке (top=0) с указанным поведением».
Мы используем новую форму — с объектом настроек. Разберём её.
top: 0— координата по вертикали. 0 = самое начало страницы.behavior: 'smooth'— плавная прокрутка. Браузер сам анимирует движение: примерно 300–500 миллисекунд, с ускорением и затуханием.
Если бы мы написали window.scrollTo(0, 0) — страница «телепортировалась» бы к началу мгновенно. Пользователь теряет ощущение движения: щёлк — и он уже вверху. behavior: 'smooth' даёт более приятный опыт.
Параметр behavior: 'smooth' — часть стандарта CSSOM View, поддерживается всеми современными браузерами (Chrome, Firefox, Safari, Edge). В очень старых браузерах (IE11) он проигнорируется — прокрутка будет мгновенной. Но это не критично: сам эффект — приятная деталь, не обязательная.
Полная схема работы модуля
Итог
scroll-top.js— отдельный файл. Изолирует логику кнопки «Наверх».- Ждёт
DOMContentLoaded, затем находит кнопку поid. - Слушает
scrollнаwindowи проверяетwindow.pageYOffset. - Порог 300 пикселей — эмпирический оптимум. Раньше — раздражает, позже — не помогает.
- Использует
classList.add('show')/remove('show')— идемпотентно и стабильно. - При клике —
preventDefault()+scrollTo({ top: 0, behavior: 'smooth' }). - «Плавность» прокрутки — часть CSSOM View API, поддержана в современных браузерах.
На этом Часть III. Скрипты завершена. Все файлы CSS и JS разобраны построчно. Дальше — Часть IV. Страницы сайта. Начинаем с главной — index.html, эталона для всех остальных страниц.