Locomotive Scroll в своей основе использует виртуальный скролл, где
движение контента управляется через transform и requestAnimationFrame. В
результате стандартные события scroll и изменение размеров
элементов могут не всегда корректно отражаться в состоянии библиотеки.
Для решения этой задачи применяется ResizeObserver, который
позволяет автоматически отслеживать изменения размеров элементов и
обновлять внутреннее состояние скролла без необходимости вручную
вызывать методы обновления.
ResizeObserver — это нативный веб-API, который позволяет
следить за изменением размеров одного или нескольких элементов. Основной
синтаксис выглядит так:
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
console.log(entry.target, entry.contentRect);
}
});
observer.observe(document.querySelector('.element'));
entries — массив объектов
ResizeObserverEntry, каждый из которых содержит информацию
о целевом элементе и его текущих размерах
(contentRect).observe() добавляет элемент под наблюдение.unobserve() удаляет элемент из наблюдения, а
disconnect() отключает весь наблюдатель.Locomotive Scroll предоставляет метод .update(), который
обновляет позиции элементов и внутренние данные о скролле. При
динамическом изменении контента, добавлении новых блоков или изменении
размеров контейнеров, вызов этого метода обязателен для корректной
работы скролла. ResizeObserver позволяет делать это
автоматически.
Пример подключения:
import LocomotiveScroll from 'locomotive-scroll';
const scrollContainer = document.querySelector('#scroll-container');
const scroll = new LocomotiveScroll({
el: scrollContainer,
smooth: true
});
const resizeObserver = new ResizeObserver(() => {
scroll.update();
});
// Наблюдение за контейнером скролла и всеми динамическими блоками
resizeObserver.observe(scrollContainer);
scrollContainer.querySelectorAll('.dynamic-block').forEach(block => {
resizeObserver.observe(block);
});
scroll.update() вызывается каждый раз, когда
изменяются размеры контейнера или внутренних элементов.Одной из распространённых проблем является асинхронная подгрузка
изображений. Высота контейнера может изменяться после загрузки картинки,
что приводит к рассинхронизации скролла. В сочетании с
ResizeObserver это решается автоматически:
const images = document.querySelectorAll('img');
images.forEach(img => {
img.addEventListener('load', () => {
scroll.update();
});
});
Однако использование ResizeObserver позволяет отказаться
от ручного отслеживания load каждого изображения:
images.forEach(img => resizeObserver.observe(img));
Теперь при любом изменении размеров изображений скролл будет обновляться автоматически.
Не стоит подключать ResizeObserver к слишком большому
количеству элементов без необходимости, так как это может привести к
лишним вызовам scroll.update() и нагрузке на
производительность. Рекомендуется:
unobserve()
для очистки:resizeObserver.unobserve(dynamicElement);
disconnect().Для современных фронтенд-фреймворков, таких как Vue, React или Svelte, логика наблюдения обычно помещается в lifecycle hooks компонента. Пример на React:
import { useEffect, useRef } from 'react';
import LocomotiveScroll from 'locomotive-scroll';
export default function ScrollComponent() {
const scrollRef = useRef(null);
useEffect(() => {
const scroll = new LocomotiveScroll({
el: scrollRef.current,
smooth: true
});
const resizeObserver = new ResizeObserver(() => {
scroll.update();
});
resizeObserver.observe(scrollRef.current);
return () => {
resizeObserver.disconnect();
scroll.destroy();
};
}, []);
return (
<div id="scroll-container" ref={scrollRef}>
{/* динамический контент */}
</div>
);
}
Такой подход обеспечивает:
unobserve() или disconnect() при удалении
элементов, чтобы избежать утечек памяти.Использование ResizeObserver вместе с Locomotive Scroll
обеспечивает плавную и корректную работу виртуального скролла даже в
условиях динамически изменяемого контента, избавляя от необходимости
ручного обновления и повышая стабильность визуальных эффектов.