Е

Техническое руководство проекта secserpr.ru

Как собирался сайт — по шагам, с кодом и пояснениями
← На сайт
Руководство › Часть III. Скрипты › 10. scroll-top.js

10. scroll-top.js — логика кнопки «Наверх»

Последний файл скриптов. Маленький — меньше двадцати строк, но с несколькими важными решениями: где и как отслеживать прокрутку, почему именно 300 пикселей, и как плавно вернуться наверх.

Зачем отдельный файл

Логика кнопки «Наверх» могла бы жить в script.js, как и модули бургера и активной ссылки. Но мы вынесли её в отдельный файл. Тому есть три причины.

Правило: сначала важное, потом удобства

Порядок скриптов в проекте: script.js (бургер, активная ссылка) → scroll-top.js (кнопка «Наверх»). Первое — обязательное для навигации. Второе — приятное дополнение. Если у пользователя медленный интернет, он сначала получит рабочее меню, потом — кнопку «Наверх».

Полный файл

scroll-top.js
document.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: общий каркас

JavaScript
document.addEventListener('DOMContentLoaded', function () {
  // ...
});

Такой же каркас, как в script.js: дожидаемся готовности DOM, потом запускаем код. Разберём подробности.

document — объект страницы. Через него подписываемся на события.

addEventListener('DOMContentLoaded', …) — подписка на событие «DOM построен». Сработает, когда браузер прочитал HTML и создал все элементы. Картинки, шрифты, стили могут быть ещё не загружены — это нормально, они для нашей логики не нужны.

Зачем дожидаться? Если запустить код раньше, он попытается найти кнопку #scrollToTopBtn — а её ещё нет в DOM. querySelector вернёт null, дальше ничего не произойдёт. Ждём, чтобы кнопка уже была в дереве.

Блок 2: поиск кнопки

JavaScript
const btn = document.getElementById('scrollToTopBtn');
if (!btn) return;

document.getElementById('scrollToTopBtn') — поиск по идентификатору. В HTML кнопка выглядит так:

HTML
<button id="scrollToTopBtn" class="scroll-top-btn" aria-label="Наверх">

Обратите внимание: id="scrollToTopBtn" — это идентификатор. Он должен быть единственным на всей странице. Именно поэтому для кнопки «Наверх» используется getElementById, а не querySelector: метод подчёркивает, что элемент уникален.

id vs class — когда что использовать

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: отслеживание прокрутки

JavaScript
window.addEventListener('scroll', function () {
  if (window.pageYOffset > 300) {
    btn.classList.add('show');
  } else {
    btn.classList.remove('show');
  }
});

Сердце модуля. Отслеживаем прокрутку страницы и переключаем кнопку между «скрыто» и «показано».

Слушаем прокрутку окна

JavaScript
window.addEventListener('scroll', function () {

window — глобальный объект окна браузера. Не document (страница), а именно окно. Прокручивается всё окно, а не отдельный элемент. Поэтому подписываемся на window.

Событие 'scroll' срабатывает при каждой прокрутке. Пользователь крутит колесо, тапает по экрану, двигает скроллбар — событие повторяется десятки раз в секунду. Наша функция выполнится столько же раз.

Осторожно с частотой событий

Событие scroll может срабатывать очень часто. Если внутри обработчика делать что-то тяжёлое (например, перебирать сотни DOM-элементов) — страница начнёт «тормозить». Мы делаем простую проверку и меняем один класс — это дёшево. Но правило на будущее: чем меньше работы в обработчике scroll, тем лучше.

Проверка величины прокрутки

JavaScript
if (window.pageYOffset > 300) {

window.pageYOffset — сколько пикселей пользователь прокрутил по вертикали. На верхней точке страницы значение 0. Чем ниже — тем больше.

Верх страницы pageYOffset = 0 ───────────────────── Прокрутили на 100px pageYOffset = 100 ───────────────────── Прокрутили на 300px pageYOffset = 300 ← порог ───────────────────── Прокрутили на 800px pageYOffset = 800

Почему именно 300? Эмпирическое значение. Если показывать кнопку слишком рано (например, на 50 пикселях) — она выскакивает при лёгком движении, раздражает. Если слишком поздно (на 1000) — пользователь уже устал крутить, а подсказки нет. 300 пикселей — примерно один экран мобильного — золотая середина.

Переключение класса

JavaScript
btn.classList.add('show');
// …
btn.classList.remove('show');

Два действия в зависимости от порога:

А CSS (файл scroll-top.css) в ответ на класс show меняет три свойства: opacity: 1, visibility: visible, transform: translateY(0). Кнопка плавно «выглядывает» снизу.

Почему add и remove, а не toggle

В модуле бургера мы использовали classList.toggle('active') — «переключи». Здесь же используем add и remove раздельно. Причина: условие сложнее. При каждом событии scroll мы точно знаем, должно ли быть состояние «показано». toggle бы переключал класс при каждом событии — а событий десятки в секунду. Кнопка бы мигала. add и remove — идемпотентны: если класс уже есть, add ничего не сделает. Стабильное поведение.

Блок 4: обработка клика

JavaScript
btn.addEventListener('click', function (e) {
  e.preventDefault();
  window.scrollTo({ top: 0, behavior: 'smooth' });
});

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

Отменяем стандартное поведение

JavaScript
e.preventDefault();

e — объект события. Мы получаем его как параметр функции. Он содержит все детали произошедшего: что было, где, когда.

e.preventDefault() — «отмени стандартное поведение». Но у <button> стандартного поведения нет. Так зачем вызывать preventDefault?

Это страховка на будущее. Если однажды кнопку превратят в ссылку <a href="#"> — браузер попытается перейти по адресу «#», добавит решётку в URL и «прыгнет» наверх. preventDefault предотвращает это заранее. Код готов к изменениям.

Пример: что было бы без preventDefault

Представим кнопку как <a href="#">↑</a>. Пользователь кликает. Без preventDefault: браузер добавляет # в адресную строку, страница «прыгает» наверх моментально — без плавности, которую мы хотели. И в истории появляется лишняя запись. С preventDefault — ничего этого не происходит.

Плавная прокрутка к началу

JavaScript
window.scrollTo({ top: 0, behavior: 'smooth' });

window.scrollTo() — «прокрути окно к указанным координатам». У метода две формы вызова:

Мы используем новую форму — с объектом настроек. Разберём её.

Если бы мы написали window.scrollTo(0, 0) — страница «телепортировалась» бы к началу мгновенно. Пользователь теряет ощущение движения: щёлк — и он уже вверху. behavior: 'smooth' даёт более приятный опыт.

Откуда scrollTo знает про smooth

Параметр behavior: 'smooth' — часть стандарта CSSOM View, поддерживается всеми современными браузерами (Chrome, Firefox, Safari, Edge). В очень старых браузерах (IE11) он проигнорируется — прокрутка будет мгновенной. Но это не критично: сам эффект — приятная деталь, не обязательная.

Полная схема работы модуля

ЗАГРУЗКА СТРАНИЦЫ │ ▼ DOM построен → срабатывает DOMContentLoaded │ ▼ Находим кнопку #scrollToTopBtn │ ├─── кнопки нет ──► выход │ └─── кнопка есть ─► подписываемся на события │ ├─── 'scroll' (на window) │ │ │ ▼ │ pageYOffset > 300? │ │ │ ├─── да ──► add класс 'show' │ │ (CSS показывает кнопку) │ │ │ └─── нет ─► remove класс 'show' │ (CSS скрывает кнопку) │ └─── 'click' (на кнопке) │ ▼ preventDefault() scrollTo(top: 0, smooth) │ ▼ страница плавно едет наверх

Итог

На этом Часть III. Скрипты завершена. Все файлы CSS и JS разобраны построчно. Дальше — Часть IV. Страницы сайта. Начинаем с главной — index.html, эталона для всех остальных страниц.