RequestAnimationFrame

requestAnimationFrame — это встроенный метод JavaScript, который оптимизирован для выполнения анимаций в браузере. В отличие от таймеров setTimeout и setInterval, он синхронизирован с частотой обновления экрана, что обеспечивает более плавное движение и экономию ресурсов.

При создании плавного скролла requestAnimationFrame используется для пошагового изменения позиции прокрутки в течение нескольких кадров, создавая эффект плавного перехода от одной позиции к другой.


Структура базового цикла анимации

Для реализации плавного скролла с requestAnimationFrame обычно создаётся функция анимации, которая:

  1. Вычисляет текущую позицию скролла.
  2. Рассчитывает новую позицию на основе целевой точки и времени.
  3. Устанавливает позицию с помощью window.scrollTo или element.scrollTop.
  4. Повторно вызывает себя через requestAnimationFrame, пока цель не достигнута.

Пример базовой функции:

function smoothScroll(targetPosition, duration) {
    const startPosition = window.scrollY;
    const distance = targetPosition - startPosition;
    let startTime = null;

    function animation(currentTime) {
        if (startTime === null) startTime = currentTime;
        const timeElapsed = currentTime - startTime;
        const run = easeInOutQuad(timeElapsed, startPosition, distance, duration);
        window.scrollTo(0, run);
        if (timeElapsed < duration) {
            requestAnimationFrame(animation);
        }
    }

    function easeInOutQuad(t, b, c, d) {
        t /= d / 2;
        if (t < 1) return c / 2 * t * t + b;
        t--;
        return -c / 2 * (t * (t - 2) - 1) + b;
    }

    requestAnimationFrame(animation);
}

Здесь ключевые моменты:

  • startPosition — начальная координата скролла.
  • distance — разница между текущей и целевой позицией.
  • easeInOutQuad — функция сглаживания движения, создающая ускорение и замедление.
  • requestAnimationFrame(animation) — рекурсивно вызывает анимацию на каждом кадре.

Выбор функции сглаживания

Функция сглаживания (easing) влияет на ощущение движения. Наиболее популярные варианты:

  1. Linear — равномерная скорость, без ускорения или замедления.

    function linear(t, b, c, d) {
        return c * t / d + b;
    }
  2. EaseInQuad — ускорение в начале, замедление в конце:

    function easeInQuad(t, b, c, d) {
        t /= d;
        return c * t * t + b;
    }
  3. EaseOutQuad — замедление в конце:

    function easeOutQuad(t, b, c, d) {
        t /= d;
        return -c * t * (t - 2) + b;
    }
  4. EaseInOutQuad — комбинированное ускорение и замедление:

    function easeInOutQuad(t, b, c, d) {
        t /= d / 2;
        if (t < 1) return c / 2 * t * t + b;
        t--;
        return -c / 2 * (t * (t - 2) - 1) + b;
    }

Правильный выбор функции позволяет добиться естественного движения и избежать резких скачков.


Обработка динамических целей

При работе с динамическим контентом важно пересчитывать позицию цели перед запуском анимации, так как offsetTop элементов может изменяться при изменении DOM.

const targetElement = document.querySelector('#section3');
const targetPosition = targetElement.getBoundingClientRect().top + window.scrollY;
smoothScroll(targetPosition, 800);

Использование getBoundingClientRect().top гарантирует корректную позицию относительно текущего скролла, независимо от изменений в DOM.


Прерывание и отмена анимации

Для управления анимацией полезно хранить идентификатор, возвращаемый requestAnimationFrame, чтобы иметь возможность её прервать:

let animationId;

function startScroll() {
    animationId = requestAnimationFrame(animation);
}

function stopScroll() {
    cancelAnimationFrame(animationId);
}

Это особенно актуально, если пользователь начинает ручную прокрутку или происходит изменение целевого элемента во время анимации.


Оптимизация производительности

  • Избегать частого вызова scrollTo для мелких изменений. Использовать сглаживание с интервалом кадров.
  • Считать позиции и размеры элементов заранее, если они не меняются динамически.
  • Минимизировать работу с DOM внутри функции анимации. Лучше хранить значения в переменных.

Совместимость с мобильными устройствами

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

  • Высокие значения duration могут восприниматься медленно на слабых устройствах.
  • Прерывание анимации при касании пальцем обеспечивает естественный UX.

Интеграция с библиотекой Smooth Scroll

Smooth Scroll использует requestAnimationFrame как ядро для анимации. В библиотеке:

  • Целевая позиция вычисляется с учётом смещения (offset) и текущего скролла.
  • Анимация запускается через рекурсивный вызов requestAnimationFrame.
  • Для каждой цели применяется функция сглаживания, выбираемая через опции (linear, easeInOutCubic и др.).
  • Поддерживается автоматическое прерывание анимации при пользовательском скролле.

Пример инициализации Smooth Scroll с использованием requestAnimationFrame:

const scroll = new SmoothScroll('a[href*="#"]', {
    speed: 600,
    speedAsDuration: true,
    easing: 'easeInOutCubic',
    updateURL: false
});