Что такое IntersectionObserver и для чего он используется?
Что это такое
IntersectionObserver — это нативный браузерный API, который позволяет асинхронно отслеживать изменение видимости целевого элемента относительно области просмотра (viewport) или указанного родительского контейнера (root). В отличие от подписки на событие scroll с последующим вызовом getBoundingClientRect(), наблюдатель работает вне основного потока рендеринга и не вызывает принудительный reflow, поэтому он гораздо производительнее.
Как создать наблюдателя
Наблюдатель создаётся конструктором IntersectionObserver, который принимает callback-функцию и объект настроек. Callback вызывается браузером каждый раз, когда меняется степень пересечения хотя бы одного из отслеживаемых элементов с корневым контейнером.
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log('Элемент попал во viewport');
}
});
});
const target = document.querySelector('.card');
observer.observe(target);
Основные параметры
- root — элемент-контейнер, относительно которого считается пересечение. Если null, используется viewport браузера.
- rootMargin — отступы вокруг root в формате CSS-margin (например '200px 0px'), позволяют «расширить» зону срабатывания заранее, до фактического появления элемента.
- threshold — число или массив чисел от 0 до 1, задающих долю видимости элемента, при которой должен сработать callback. threshold: 0.5 означает срабатывание при видимости половины элемента.
Каждая запись в entries (IntersectionObserverEntry) содержит isIntersecting, intersectionRatio, boundingClientRect, target и rootBounds.
Практическое применение
Наиболее частые сценарии использования:
- Ленивая загрузка изображений и iframe вместо атрибута loading="lazy", когда нужен более гибкий контроль.
- Бесконечная прокрутка (infinite scroll) — подгрузка следующей страницы данных при появлении элемента-триггера в конце списка.
- Аналитика видимости баннеров и рекламных блоков (viewability tracking).
- Анимации появления элементов при скролле (reveal on scroll).
- Управление тяжёлыми виджетами (видео, карты) — пауза при выходе из viewport для экономии ресурсов.
Производительность и очистка
Главное преимущество перед связкой scroll + getBoundingClientRect в том, что вычисления пересечения браузер выполняет асинхронно и оптимизированно, не блокируя основной поток и не провоцируя layout thrashing. После того как элемент больше не нужно отслеживать, важно вызывать observer.unobserve(target) или observer.disconnect(), чтобы не удерживать ссылки на DOM-узлы и избежать утечек памяти.
Что хочет услышать интервьюер
Кандидат объясняет, что IntersectionObserver асинхронный и не блокирует основной поток, в отличие от scroll-обработчиков
Знание ключевых опций: root, rootMargin, threshold и их влияния на срабатывание callback
Понимание структуры IntersectionObserverEntry (isIntersecting, intersectionRatio, boundingClientRect)
Приводит реальные кейсы: ленивая загрузка, infinite scroll, viewability-аналитика
Упоминает необходимость отписки через unobserve/disconnect для предотвращения утечек памяти
Пример: Ленивая загрузка изображений
const images = document.querySelectorAll('img[data-src]');
const lazyObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
// после загрузки картинку больше не нужно отслеживать
observer.unobserve(img);
}
});
}, {
rootMargin: '200px 0px', // начинаем грузить заранее, до входа в viewport
});
images.forEach((img) => lazyObserver.observe(img));
Пример: Бесконечная прокрутка списка
const sentinel = document.querySelector('#load-more-trigger');
const infiniteScrollObserver = new IntersectionObserver(async (entries) => {
const [entry] = entries;
if (entry.isIntersecting) {
// элемент-триггер показался — подгружаем следующую страницу
await loadNextPage();
}
}, {
threshold: 1, // триггер должен быть виден полностью
});
infiniteScrollObserver.observe(sentinel);
Типичные ошибки
Путают IntersectionObserver с ResizeObserver или MutationObserver, приписывая ему не те задачи
Забывают, что threshold: 1 означает полную видимость элемента, а не единичное срабатывание
Не вызывают unobserve/disconnect после того, как элемент больше не нужно отслеживать, что приводит к лишним вычислениям и утечкам
Считают, что callback вызывается синхронно сразу после observe(), хотя первое срабатывание тоже асинхронно
Пытаются использовать один наблюдатель с разными наборами threshold для разных целей вместо создания нескольких наблюдателей


