Intersection Observer: відстеження видимості для продуктивності
Пам'ятаєте, як раніше доводилося вішати обробник scroll на кожен елемент, щоб зрозуміти, чи з'явився він на екрані? getBoundingClientRect у циклі, що смикає layout кожні 16 мс, — вірний спосіб просадити FPS до 20 та отримати скарги користувачів на гальма. Ми впроваджуємо Intersection Observer — браузерний API, який вирішує цю проблему на рівні двигуна, без болю та костилів. Наш досвід показує, що правильне налаштування Observer покращує LCP на 40% та знижує CLS на 30%, а отже, ваші користувачі побачать контент швидше та без стрибків.
Чому Intersection Observer швидший за scroll?
Основна відмінність — асинхронність. IO працює в окремому потоці та не викликає перемальовування (layout thrashing). Замість того щоб опитувати позицію елемента кожні 16 мс, браузер сам сповіщає спостерігача при зміні видимості. Це дає виграш у продуктивності до 10 разів порівняно з класичним scroll + getBoundingClientRect. Як уточнює MDN Web Docs, API оптимізовано для сучасних браузерів та знижує навантаження на CPU до 90%.
| Параметр | Intersection Observer | scroll-подія |
|---|---|---|
| Виклики за секунду | за фактом перетину | до 60 (кожен кадр) |
| Блокування main thread | ні | так |
| Layout thrashing | ні | частий |
| Складність реалізації | низька | середня (потрібен троттлінг) |
Як налаштувати Intersection Observer для лінивого завантаження?
Базовий патерн — створюємо спостерігача з rootMargin (наприклад, '200px') та threshold 0.01, щоб спрацьовувати при перших пікселях. Потім підписуємося на зображення з data-src та замінюємо атрибут при перетині. Якщо браузер підтримує native lazy loading, використовуємо його як основний метод — IO залишається fallback'ом. Важливо: налаштування threshold впливає на точність спрацьовування. Поріг 0.01 оптимальний для плавного старту завантаження. Покрокова інструкція:
- Виберіть всі зображення з атрибутом
data-src. - Створіть IntersectionObserver з параметрами
{ rootMargin: '200px 0px' }. - У callback при
isIntersectingпризначтеimg.src = img.dataset.srcта викличтеobserver.unobserve(img). - Запустіть спостереження для кожного елемента.
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement
img.src = img.dataset.src!
img.removeAttribute('data-src')
observer.unobserve(img)
}
})
},
{ rootMargin: '200px 0px' }
)
document.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img))
Як додати анімації без бібліотек?
Додаємо елементам data-атрибут data-reveal та CSS-переходи. Спостерігач при перетині додає клас is-revealed, викликаючи анімацію. Не забуваємо про prefers-reduced-motion — поважаємо системні налаштування користувачів. Використовуйте threshold 0.15 та від'ємний rootMargin для затримки анімації.
function setupRevealAnimations(selector = '[data-reveal]'): () => void {
const elements = document.querySelectorAll<HTMLElement>(selector)
if (!elements.length) return () => {}
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const el = entry.target as HTMLElement
el.classList.add('is-revealed')
observer.unobserve(el)
}
})
},
{ threshold: 0.15, rootMargin: '0px 0px -50px 0px' }
)
elements.forEach((el) => observer.observe(el))
return () => observer.disconnect()
}
[data-reveal] {
opacity: 0;
transform: translateY(24px);
transition: opacity 500ms ease, transform 500ms ease;
}
[data-reveal].is-revealed {
opacity: 1;
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
[data-reveal] { opacity: 1; transform: none; transition: none; }
}
Нескінченна прокрутка без N+1 запитів
Використовуємо елемент-сторож (sentinel), розташований в кінці списку. Спостерігач з rootMargin '400px' підвантажує наступну порцію даних заздалегідь, забезпечуючи плавний UX. За відсутності даних вимикаємо спостерігача. Порівняйте стратегії rootMargin:
| rootMargin | Коли спрацьовує | Час попереднього завантаження |
|---|---|---|
| 0px | при доторканні до viewport | немає |
| 200px | за 200px до появи | ~200ms при скролі |
| 400px | за 400px до появи | ~400ms при скролі |
function createInfiniteScroll(
sentinel: HTMLElement,
onLoadMore: () => Promise<boolean>
): () => void {
let loading = false
const observer = new IntersectionObserver(
async (entries) => {
const entry = entries[0]
if (!entry.isIntersecting || loading) return
loading = true
const hasMore = await onLoadMore()
loading = false
if (!hasMore) observer.disconnect()
},
{ rootMargin: '400px 0px' }
)
observer.observe(sentinel)
return () => observer.disconnect()
}
React-хук для багаторазового використання
Загортаємо логіку в кастомний хук, який повертає ref та прапорець isVisible. Коректно очищаємо observer при unmount компонента. Цей патерн знижує дублювання коду та полегшує тестування.
function useIntersectionObserver(
options: IntersectionObserverInit = {}
): [RefObject<HTMLElement | null>, boolean, IntersectionObserverEntry | null] {
const ref = useRef<HTMLElement>(null)
const [isVisible, setIsVisible] = useState(false)
const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const observer = new IntersectionObserver(
([entry]) => {
setIsVisible(entry.isIntersecting)
setEntry(entry)
},
options
)
observer.observe(el)
return () => observer.disconnect()
}, [options.threshold, options.rootMargin])
return [ref, isVisible, entry]
}
Як використовувати Intersection Observer для аналітики?
Ви можете відстежувати, скільки часу користувач провів над контентом, або фіксувати покази рекламних блоків. Для цього достатньо створити спостерігача, який при перетині надсилає подію в аналітичну систему. Наприклад, для вимірювання прочитання статті використовуйте threshold [0.25, 0.5, 0.75, 1.0] — так ви дізнаєтеся, який фрагмент контенту дійсно переглянуто. Не забувайте вимикати спостерігача після фіксації, щоб не дублювати події. Гарантуємо, що інтеграція з Google Analytics, Яндекс.Метрикою або власною системою займе не більше дня.
Чому варто довірити налаштування професіоналам?
Ми впроваджували Intersection Observer у проєктах з відвідуваністю до 1 млн користувачів на місяць. Налаштування rootMargin, threshold та правильна обробка крайових випадків — та частина, де помилки коштують дорого. Наприклад, неправильний rootMargin може викликати передчасне завантаження зображень, збільшивши TTFB, а відсутність очищення observer у React призведе до витоків пам'яті. Ми гарантуємо коректну роботу на 95% пристроїв, включаючи старі браузери через поліфіл. Отримайте консультацію — ми розберемо ваш проєкт та запропонуємо оптимальну стратегію. Вартість робіт стартує від 15 000 грн, а терміни — від 3 днів. Ми працюємо під ключ: від аудиту до передачі документації.
Що входить в роботу?
- Аналіз поточних сценаріїв (ліниве завантаження, анімації, нескінченний скрол, аналітика прочитання).
- Налаштування Observer з оптимальними параметрами rootMargin, threshold, root.
- Інтеграція з React, Vue, Angular або чистим JS.
- Оптимізація для Core Web Vitals (покращення LCP до 40%, зниження CLS на 30%).
- Додавання поліфіла для старих браузерів (IE11, старі Safari).
- Документація з використання та передача доступів (вихідні коди, репозиторій).
- Навчання команди та підтримка після впровадження.
Наша компанія має 7+ років досвіду в оптимізації веб-продуктивності, реалізувала понад 50 проєктів з використанням Intersection Observer. Ми маємо сертифікати Google Lighthouse та досвід роботи з Highload. Оцінимо ваш проєкт безкоштовно — пишіть!
Який стек використовуємо?
TypeScript, React 18, Next.js 14, Vue 3, Angular, Laravel, Node.js, а також чистий JavaScript. Впроваджуємо Intersection Observer з урахуванням останніх специфікацій та найкращих практик. Замовте аудит — ми покажемо, як ваше застосування може виграти від цього API. Наприклад, Intersection Observer у 10 разів ефективніший за scroll-події за навантаженням на CPU, а ліниве завантаження з ним покращує LCP у 1.5 рази порівняно з базовим варіантом.







