Server-side rendering

Locomotive Scroll — это библиотека для создания плавного скроллинга с эффектами параллакса и анимаций. При использовании Server-side rendering (SSR) возникают специфические нюансы, связанные с тем, что библиотека тесно взаимодействует с DOM. Поскольку на сервере DOM отсутствует, необходимо учитывать моменты инициализации и условного рендера скриптов.


Проблемы при SSR

  1. Отсутствие объекта window и document на сервере Locomotive Scroll напрямую обращается к window и document. При рендеринге на сервере это вызывает ошибки:

    const scroll = new LocomotiveScroll({
        el: document.querySelector('#scroll-container'),
        smooth: true
    });

    На сервере document не существует, что приведёт к падению сборки.

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

    let scroll;
    if (typeof window !== 'undefined') {
        const LocomotiveScroll = require('locomotive-scroll').default;
        scroll = new LocomotiveScroll({
            el: document.querySelector('#scroll-container'),
            smooth: true
        });
    }

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

  3. Пререндеринг контента и расчёт размеров Locomotive Scroll рассчитывает размеры элементов при инициализации. На сервере это невозможно. При SSR рекомендуется:

    • использовать placeholder контейнеры;
    • пересчитывать скролл после монтирования компонента на клиенте с помощью метода update():
    scroll.update();

Интеграция с React и Next.js

При использовании Next.js или других фреймворков с SSR ключевым является разделение серверного и клиентского кода.

Пример с использованием React hooks:

import { useEffect, useRef } from 'react';
import LocomotiveScroll from 'locomotive-scroll';
import 'locomotive-scroll/dist/locomotive-scroll.css';

export default function SmoothScrollContainer({ children }) {
    const scrollRef = useRef(null);

    useEffect(() => {
        const scroll = new LocomotiveScroll({
            el: scrollRef.current,
            smooth: true,
            multiplier: 1.2
        });

        return () => scroll.destroy();
    }, []);

    return (
        
{children}
); }
  • useEffect гарантирует выполнение кода только на клиенте, после того как DOM готов.
  • ref используется для безопасного доступа к элементу контейнера.
  • Вызов destroy() предотвращает утечки памяти при размонтировании компонента.

Обновление и динамический контент

Если на странице динамически меняется контент (например, при загрузке данных с API), важно пересчитывать скролл:

useEffect(() => {
    if (scrollRef.current) {
        scrollRef.current.update();
    }
}, [dynamicContent]);
  • update() сообщает Locomotive Scroll о новых элементах и их позициях.
  • Для больших страниц с множеством анимаций рекомендуется вызывать update() после каждого изменения DOM, которое влияет на размеры контейнера.

Особенности параллакса и анимаций при SSR

  1. Атрибуты data-scroll и data-scroll-speed Их можно рендерить на сервере, так как это обычные HTML-атрибуты. Анимации будут работать только на клиенте после инициализации Locomotive Scroll.

  2. Позиционирование элементов Locomotive Scroll использует transform: translate3d(...). При SSR элементы остаются в исходных позициях до монтирования на клиенте. Важно учитывать визуальные смещения при первом рендере страницы, чтобы избежать “скачков” контента.


Применение с другими библиотеками анимаций

Locomotive Scroll можно интегрировать с GSAP, Framer Motion или другими библиотеками. При SSR:

  • Инициализацию анимаций делать только на клиенте.
  • Использовать useLayoutEffect или componentDidMount для синхронизации с DOM.

Пример с GSAP:

useEffect(() => {
    const scroll = new LocomotiveScroll({ el: scrollRef.current, smooth: true });

    gsap.from('.animate', {
        scrollTrigger: {
            scroller: scrollRef.current,
            trigger: '.animate',
            start: 'top 80%',
            end: 'bottom 20%',
            scrub: true
        },
        y: 50,
        opacity: 0
    });

    return () => scroll.destroy();
}, []);
  • scroller указывает на контейнер Locomotive Scroll.
  • Все ScrollTrigger настройки должны быть выполнены после инициализации скролла.

Практические рекомендации

  • Не подключать Locomotive Scroll на сервере.
  • Выполнять пересчет размеров после всех динамических обновлений DOM.
  • Использовать ref и useEffect для безопасной работы с элементами.
  • Атрибуты data-scroll безопасно рендерить на сервере, они будут активны после монтирования.
  • Уничтожение экземпляра с destroy() предотвращает утечки памяти и некорректное поведение при смене страниц.

Производительность при SSR

  • Минимизировать количество пересчетов с помощью update() только при необходимости.
  • Для тяжелых страниц с большим количеством анимаций использовать throttle или debounce при обработке событий resize и scroll.
  • Рассматривать возможность ленивой инициализации для частей страницы, которые не видны сразу, чтобы ускорить Time to Interactive (TTI).