Intersection Observer

Intersection Observer — это современный API браузера для отслеживания пересечения DOM-элементов с областью видимости или другим родительским элементом. В Atomico он интегрируется естественно через реактивные хуки и предоставляет удобные средства для управления состоянием видимости компонентов.


Создание Intersection Observer

Для использования Intersection Observer необходимо создать наблюдатель и связать его с целевым элементом. В Atomico это делается через хук useEffect или через реактивные свойства:

import { c, useRef, useEffect } from "atomico";

function VisibilityComponent() {
    const ref = useRef(null);

    useEffect(() => {
        const observer = new IntersectionObserver(
            ([entry]) => {
                if (entry.isIntersecting) {
                    console.log("Элемент виден");
                } else {
                    console.log("Элемент скрыт");
                }
            },
            {
                root: null, // отслеживание относительно viewport
                threshold: 0.5 // срабатывание, когда 50% элемента видны
            }
        );

        if (ref.current) observer.observe(ref.current);

        return () => observer.disconnect();
    }, []);

    return <div ref={ref} style={{ height: "200px", background: "#ccc" }}>Наблюдаемый элемент</div>;
}

export const visibilityComponent = c(VisibilityComponent);

Ключевые моменты:

  • root — элемент, относительно которого отслеживается видимость. null соответствует viewport.
  • threshold — процент видимой области элемента, при котором срабатывает callback. Может быть числом или массивом.
  • observer.disconnect() — обязательно для очистки ресурсов.

Интеграция с реактивностью Atomico

Atomico поддерживает реактивные переменные через useState и useEffect. Можно хранить состояние видимости компонента в реактивной переменной:

import { c, useState, useRef, useEffect } from "atomico";

function ReactiveVisibility() {
    const [visible, setVisible] = useState(false);
    const ref = useRef(null);

    useEffect(() => {
        const observer = new IntersectionObserver(([entry]) => {
            setVisible(entry.isIntersecting);
        }, { threshold: 0.1 });

        if (ref.current) observer.observe(ref.current);

        return () => observer.disconnect();
    }, []);

    return (
        <div ref={ref} style={{ height: "150px", background: visible ? "green" : "red" }}>
            {visible ? "Виден" : "Скрыт"}
        </div>
    );
}

export const reactiveVisibility = c(ReactiveVisibility);

Преимущество такой интеграции — автоматический ререндер компонента при изменении состояния visible.


Отслеживание нескольких элементов

Для наблюдения сразу нескольких элементов удобно использовать массив рефов или делегировать регистрацию через querySelectorAll:

import { c, useEffect } from "atomico";

function MultiObserver() {
    useEffect(() => {
        const elements = document.querySelectorAll(".observe");
        const observer = new IntersectionObserver(
            (entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        entry.target.style.background = "blue";
                    } else {
                        entry.target.style.background = "gray";
                    }
                });
            },
            { threshold: 0.2 }
        );

        elements.forEach(el => observer.observe(el));

        return () => observer.disconnect();
    }, []);

    return (
        <>
            <div className="observe" style={{ height: "100px", margin: "10px" }}>Элемент 1</div>
            <div className="observe" style={{ height: "100px", margin: "10px" }}>Элемент 2</div>
            <div className="observe" style={{ height: "100px", margin: "10px" }}>Элемент 3</div>
        </>
    );
}

export const multiObserver = c(MultiObserver);

Особенности:

  • Callback вызывается для каждого наблюдаемого элемента отдельно.
  • Можно использовать разные threshold для разных целей.

Параметры конфигурации

Intersection Observer имеет несколько ключевых опций:

  • root — контейнер для отслеживания. Если null, используется viewport.
  • rootMargin — смещение области наблюдения. Пример: "0px 0px -50px 0px".
  • threshold — срабатывание при видимой доле элемента, может быть массивом [0, 0.25, 0.5, 1].

Пример с rootMargin и массивом threshold:

const observer = new IntersectionObserver(([entry]) => {
    console.log(entry.intersectionRatio);
}, {
    root: document.querySelector(".scroll-container"),
    rootMargin: "0px 0px -20px 0px",
    threshold: [0, 0.25, 0.5, 0.75, 1]
});

Lazy loading изображений

Одна из популярных практик — ленивое подгружение изображений при появлении в viewport:

import { c, useRef, useEffect } from "atomico";

function LazyImage({ src, placeholder }) {
    const ref = useRef(null);

    useEffect(() => {
        const observer = new IntersectionObserver(([entry]) => {
            if (entry.isIntersecting && ref.current.dataset.src) {
                ref.current.src = ref.current.dataset.src;
                ref.current.removeAttribute("data-src");
                observer.unobserve(ref.current);
            }
        }, { threshold: 0.1 });

        if (ref.current) observer.observe(ref.current);

        return () => observer.disconnect();
    }, []);

    return <img ref={ref} data-src={src} src={placeholder} alt="Lazy loaded" />;
}

export const lazyImage = c(LazyImage);

Преимущества:

  • Экономия трафика.
  • Ускорение рендеринга страницы.
  • Простая интеграция с реактивным рендерингом Atomico.

Управление производительностью

  • Ограничение числа наблюдаемых элементов уменьшает нагрузку на Intersection Observer.
  • Использование disconnect при удалении элементов предотвращает утечки памяти.
  • Настройка threshold и rootMargin позволяет точнее контролировать срабатывание и минимизировать лишние callback.

Совместимость с SSR и Hydration

При серверном рендеринге необходимо проверять доступность window:

useEffect(() => {
    if (typeof window === "undefined") return;
    // Инициализация Intersection Observer
}, []);

Это предотвращает ошибки при рендеринге на сервере и делает компонент безопасным для универсальных приложений на Atomico.


Выводы по использованию в Atomico

  • Intersection Observer легко интегрируется через useRef и useEffect.
  • Подходит для ленивой загрузки, анимаций появления, отслеживания видимости компонентов.
  • Комбинируется с реактивными состояниями Atomico для динамического изменения DOM и стилей.

Использование этого подхода позволяет создавать высокопроизводительные и отзывчивые интерфейсы без лишних обработчиков скролла и сложной логики расчёта позиции элементов.