ResizeObserver для автообновления

Locomotive Scroll в своей основе использует виртуальный скролл, где движение контента управляется через transform и requestAnimationFrame. В результате стандартные события scroll и изменение размеров элементов могут не всегда корректно отражаться в состоянии библиотеки. Для решения этой задачи применяется ResizeObserver, который позволяет автоматически отслеживать изменения размеров элементов и обновлять внутреннее состояние скролла без необходимости вручную вызывать методы обновления.


Основы 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

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() и нагрузке на производительность. Рекомендуется:

  • Наблюдать за основным контейнером скролла.
  • Подключать только динамические элементы, чьи размеры могут изменяться (изображения, блоки с текстом, видео).
  • При удалении элементов из DOM использовать unobserve() для очистки:
resizeObserver.unobserve(dynamicElement);
  • Если элементы полностью удаляются и наблюдение больше не требуется, использовать disconnect().

Использование ResizeObserver внутри компонентов

Для современных фронтенд-фреймворков, таких как 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>
  );
}

Такой подход обеспечивает:

  • Автоматическое обновление скролла при изменении размеров контейнера.
  • Очистку ресурсов при размонтировании компонента.
  • Поддержку динамического контента без ручного вмешательства.

Ключевые моменты работы с ResizeObserver в Locomotive Scroll

  • Автообновление — основная задача ResizeObserver при интеграции с Locomotive Scroll.
  • scroll.update() — метод, вызываемый при любых изменениях размеров для пересчёта позиций элементов.
  • Оптимизация — наблюдать только за элементами, размеры которых реально могут измениться.
  • Асинхронный контент — изображения, видео, динамический текст автоматически поддерживаются без дополнительных обработчиков событий.
  • Очистка ресурсов — обязательно использовать unobserve() или disconnect() при удалении элементов, чтобы избежать утечек памяти.

Использование ResizeObserver вместе с Locomotive Scroll обеспечивает плавную и корректную работу виртуального скролла даже в условиях динамически изменяемого контента, избавляя от необходимости ручного обновления и повышая стабильность визуальных эффектов.