useIntersectionObserver
Описание
useIntersectionObserver — хук для наблюдения за видимостью DOM-элемента через IntersectionObserver.
Подписывается на элемент из ref, по каждому изменению может вызывать колбэк, а также предоставляет лениво-активируемое поле currentEntry c последней записью наблюдателя.
Хук возвращает гибридную структуру: её можно деструктурировать как кортеж ([currentEntry]) или как объект ({ currentEntry }).
Сигнатура
function useIntersectionObserver<ElementType extends Element | null>(
ref: RefObject<ElementType | null>,
callback?: UseIntersectionObserverCallback,
): UseIntersectionObserverReturn;Параметры
ref— ссылочный объект на элемент, видимость которого нужно отслеживать.callback?— функция, вызываемая при каждом изменении; принимает актуальныйIntersectionObserverEntry.
Возвращает:
UseIntersectionObserverReturn- Объект/кортеж с полем
currentEntry: IntersectionObserverEntry | null— последняя запись наблюдателя (обновляется по мере изменений; ререндеры включаются после первого доступа к этому полю).
- Объект/кортеж с полем
Примеры
1) Проверка, находится ли элемент в зоне видимости
import { useRef, useEffect } from 'react';
import { useIntersectionObserver } from '@webeach/react-hooks';
export function Section() {
const ref = useRef<HTMLDivElement>(null);
const { currentEntry } = useIntersectionObserver(ref);
useEffect(() => {
if (currentEntry?.isIntersecting) {
console.log('Элемент виден на экране');
}
}, [currentEntry]);
return (
<div ref={ref} style={{ height: 300 }}>
Секция
</div>
);
}2) Побочный эффект только через callback
import { useRef } from 'react';
import { useIntersectionObserver } from '@webeach/react-hooks';
export function AnalyticsTracker() {
const ref = useRef<HTMLElement>(null);
useIntersectionObserver(ref, (entry) => {
if (entry.isIntersecting) {
console.log('Отправляем событие: элемент появился');
}
});
return <section ref={ref} style={{ minHeight: 400 }} />;
}3) Ленивая загрузка изображений
import { useRef, useEffect, useState } from 'react';
import { useIntersectionObserver } from '@webeach/react-hooks';
export function LazyImage({ src, alt }: { src: string; alt: string }) {
const ref = useRef<HTMLImageElement>(null);
const { currentEntry } = useIntersectionObserver(ref);
const [loaded, setLoaded] = useState(false);
useEffect(() => {
if (currentEntry?.isIntersecting) {
setLoaded(true);
}
}, [currentEntry]);
return <img ref={ref} src={loaded ? src : undefined} alt={alt} />;
}Поведение
Подписка и переподписка
- Наблюдатель подключается к
ref.currentпосле маунта и переподключается при смене элемента.
- Наблюдатель подключается к
Ленивая реактивность
currentEntry- Ререндеры для
currentEntryвключаются на первом внешнем чтении поля. Пока поле не читают, обновления не приводят к перерисовкам.
- Ререндеры для
Колбэк независим от
currentEntrycallback, если указан, вызывается при каждом изменении пересечения вне зависимости от использованияcurrentEntry.
Очистка
- Наблюдатель автоматически отключается при размонтировании компонента и при смене целевого элемента.
SSR-безопасность
- Логика подписки выполняется только в браузере; при серверном рендеринге побочные эффекты не выполняются.
Когда использовать
Ленивая загрузка
- Загружать изображения, видео или данные только при появлении элемента в области видимости.
Анимации при скролле
- Запускать анимации, когда блок появляется на экране.
Аналитика
- Отслеживать просмотры секций или баннеров без глобальных слушателей скролла.
Когда не использовать
Единичная проверка видимости
- Для одноразового вычисления позиции достаточно
getBoundingClientRect.
- Для одноразового вычисления позиции достаточно
Если важна только прокрутка окна
- Используйте глобальные события (
scroll,resize) вместо локального наблюдателя.
- Используйте глобальные события (
Частые ошибки
Отсутствие привязки
refк элементу- Если
ref.current === null, подписка не произойдёт. Убедитесь, чтоrefпередан в нужный элемент.
- Если
Ожидание ререндера без доступа к
currentEntry- Если вы используете только
callback, компонент не будет перерисовываться — это ожидаемое поведение.
- Если вы используете только
Случайная переподписка
- Следите, чтобы сам
refбыл стабильным; не создавайте новыйrefна каждый рендер.
- Следите, чтобы сам
Типизация
Экспортируемые типы
UseIntersectionObserverCallback(entry: IntersectionObserverEntry) => void.
UseIntersectionObserverReturn- Гибрид: объект
{ currentEntry: IntersectionObserverEntry | null }и кортеж[currentEntry: IntersectionObserverEntry | null].
- Гибрид: объект
UseIntersectionObserverReturnObject- Объектная форма:
{ currentEntry: IntersectionObserverEntry | null }.
- Объектная форма:
UseIntersectionObserverReturnTuple- Кортежная форма:
[currentEntry: IntersectionObserverEntry | null].
- Кортежная форма: