Что такое Intersection Observer?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Intersection Observer API: Мониторинг видимости элементов
Intersection Observer — это браузерный API, который позволяет асинхронно отслеживать, когда элемент становится видимым или невидимым в viewport браузера. Это мощный инструмент для оптимизации производительности и создания интерактивного контента.
Что такое Intersection Observer
Intersection Observer наблюдает за пересечением (intersection) элемента с видимой областью экрана или с другим элементом. Когда элемент входит или выходит из видимой области, срабатывает callback функция.
Основные применения:
- Ленивая загрузка (lazy loading) — загружай изображения только когда они видны
- Бесконечный скролл — загружай больше контента при скролле вниз
- Аналитика — отслеживай, какие элементы пользователь видел
- Вход в область экрана (animations) — запусти анимацию когда элемент видно
Синтаксис и примеры
Базовый пример: Ленивая загрузка изображений
export function LazyImage() {
const imageRef = useRef<HTMLImageElement>(null);
const [imageSrc, setImageSrc] = useState('');
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// Когда элемент видно
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement;
// Загружаем полное изображение
setImageSrc(img.dataset.src || '');
// Перестаём наблюдать, так как изображение уже загружено
observer.unobserve(img);
}
});
});
if (imageRef.current) {
observer.observe(imageRef.current);
}
return () => {
if (imageRef.current) {
observer.unobserve(imageRef.current);
}
};
}, []);
return (
<img
ref={imageRef}
src={imageSrc}
data-src="https://example.com/image.jpg"
alt="Lazy loaded image"
className="w-full h-auto"
/>
);
}
Бесконечный скролл (infinite scroll)
export function InfiniteScrollList() {
const [items, setItems] = useState<string[]>([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const sentinelRef = useRef<HTMLDivElement>(null);
const loadMore = async () => {
setLoading(true);
const newItems = await fetchItems(page);
setItems((prev) => [...prev, ...newItems]);
setPage((prev) => prev + 1);
setLoading(false);
};
useEffect(() => {
// Когда sentinel элемент видно, загружаем ещё
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && !loading) {
loadMore();
}
});
if (sentinelRef.current) {
observer.observe(sentinelRef.current);
}
return () => {
if (sentinelRef.current) {
observer.unobserve(sentinelRef.current);
}
};
}, [loading, page]);
return (
<div>
{items.map((item, i) => (
<div key={i} className="p-4 border-b border-border-1">
{item}
</div>
))}
{/* Sentinel элемент — когда он видно, загружаем ещё */}
<div ref={sentinelRef} className="p-4 text-center">
{loading && 'Loading...'}
</div>
</div>
);
}
Конфигурация Intersection Observer
Можешь настроить поведение через options:
const options = {
// root: null означает viewport (видимая область)
root: null,
// Отступ вокруг root'а
// Негативное значение = срабатывает раньше
rootMargin: '100px',
// Пороги видимости (от 0 до 1)
// 0 = даже пиксель виден
// 1 = 100% элемента видно
// [0, 0.5, 1] = срабатывает при 0%, 50%, 100%
threshold: [0, 0.5, 1],
};
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// entry.intersectionRatio показывает сколько процентов видно
console.log(`Видимо: ${entry.intersectionRatio * 100}%`);
});
}, options);
Практический пример: Анимация при скролле
export function AnimatedSection() {
const sectionRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
// Элемент видно, запускаем анимацию
setIsVisible(true);
// Необязательно, но можно перестать наблюдать
observer.unobserve(entry.target);
}
});
},
{
threshold: 0.1, // Срабатывает, когда 10% видно
rootMargin: '-50px', // Срабатывает на 50px раньше
}
);
if (sectionRef.current) {
observer.observe(sectionRef.current);
}
return () => {
if (sectionRef.current) {
observer.unobserve(sectionRef.current);
}
};
}, []);
return (
<div
ref={sectionRef}
className={cn(
'p-8 bg-surface-1 rounded-lg transition-all duration-700',
isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'
)}
>
<h2 className="text-2xl font-bold mb-4">Animated Section</h2>
<p>Эта секция будет анимирована при скролле</p>
</div>
);
}
Пример: Отслеживание видимого элемента в списке
export function ScrollSpy() {
const [activeId, setActiveId] = useState('');
useEffect(() => {
// Наблюдаем за всеми section элементами
const sections = document.querySelectorAll('section');
const observer = new IntersectionObserver(
(entries) => {
// Берём элемент с наибольшей видимостью
const mostVisible = entries.reduce((prev, current) =>
current.intersectionRatio > prev.intersectionRatio
? current
: prev
);
if (mostVisible.isIntersecting) {
setActiveId(mostVisible.target.id);
}
},
{ threshold: 0.5 }
);
sections.forEach((section) => observer.observe(section));
return () => {
sections.forEach((section) => observer.unobserve(section));
};
}, []);
return (
<nav className="sticky top-0 bg-surface-1 p-4">
<ul className="space-y-2">
{['intro', 'features', 'pricing', 'contact'].map((id) => (
<li key={id}>
<a
href={`#${id}`}
className={cn(
'block p-2 rounded transition',
activeId === id
? 'bg-surface-2 text-link font-semibold'
: 'text-content-2 hover:bg-surface-2'
)}
>
{id.charAt(0).toUpperCase() + id.slice(1)}
</a>
</li>
))}
</ul>
</nav>
);
}
Сравнение с альтернативами
Intersection Observer vs других подходов:
| Подход | Плюсы | Минусы |
|---|---|---|
| Intersection Observer | Оптимально, асинхронно, встроено | IE не поддерживает |
| Scroll event + getBoundingClientRect | Простой | Срабатывает часто, блокирует |
| window.InnerHeight + offset | Старый метод | Сложный, неточный |
Производительность
Intersection Observer — это очень эффективный API:
- Асинхронный — не блокирует основной поток
- Батчированный — несколько изменений срабатывают один раз
- Оптимизированный браузером — V8 знает как это оптимизировать
// Плохо: срабатывает на каждый пиксель скролла
window.addEventListener('scroll', () => {
document.querySelectorAll('.lazy').forEach((el) => {
if (el.getBoundingClientRect().top < window.innerHeight) {
loadImage(el);
}
});
});
// Хорошо: асинхронно, только нужные элементы
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadImage(entry.target);
observer.unobserve(entry.target);
}
});
});
document.querySelectorAll('.lazy').forEach((el) => observer.observe(el));
Браузерная поддержка
Intersection Observer поддерживается всеми современными браузерами, кроме IE11. Если нужна поддержка IE, используй полифилл:
npm install intersection-observer
import 'intersection-observer';
Выводы
- Intersection Observer — мощный инструмент для отслеживания видимости элементов
- Ленивая загрузка — экономит bandwidth и улучшает performance
- Бесконечный скролл — улучшает UX для больших списков
- Асинхронность — не блокирует основной поток
- Простой API — легко использовать в React через useEffect
Это обязательный API для modern frontend разработчика!