IntersectionObserver
IntersectionObserver це вбудований API браузера, який дозволяє відстежувати, коли елемент потрапляє в область видимості (viewport) або виходить із неї, без важких обробників scroll. Це об'єкт, який «спостерігає» за елементом і повідомляє, коли той перетинається з областю перегляду або з іншим контейнером.
Теорія
TL;DR
IntersectionObserverстежить за перетином елемента з viewport або з заданим контейнером.- Це заміна
scroll-подіям, ручним перевіркамgetBoundingClientRect()іwindow.onscroll. - Створення:
new IntersectionObserver(callback, options)плюсobserver.observe(element). - Опції:
root,rootMargin,threshold. - У колбек приходить масив
IntersectionObserverEntryзisIntersecting,intersectionRatio,target. - Зупинка:
observer.unobserve(target)для одного елемента,observer.disconnect()для всіх.
Швидкий приклад
const target = document.querySelector('#target');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log('Елемент з\'явився в зоні видимості!');
} else {
console.log('Елемент вийшов із зони видимості!');
}
});
});
observer.observe(target); // починаємо спостерігатиПростий приклад на сторінці
<div style="height: 120vh;"></div>
<div id="target" style="height: 100px; background: coral;">Я ціль</div>
<div style="height: 120vh;"></div>
<script>
const target = document.querySelector('#target');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
console.log('Елемент з\'явився в зоні видимості!');
} else {
console.log('Елемент вийшов із зони видимості!');
}
});
});
observer.observe(target); // починаємо спостерігати
</script>Коли ви прокрутите сторінку і блок потрапить на екран, у консоль виведеться:
Елемент з'явився в зоні видимості!Це чудова альтернатива:
- подіям
scroll; - ручним перевіркам через
getBoundingClientRect(); - обробникам
window.onscroll, які сильно навантажують сторінку.
Параметри конструктора
new IntersectionObserver(callback, options);| Параметр | Що робить |
|---|---|
callback | Функція, яку викликають при кожному перетині |
options.root | Контейнер, усередині якого відстежувати (за замовчуванням вікно браузера) |
options.rootMargin | Відступи (наприклад '0px 0px -100px 0px'), як у CSS margin |
options.threshold | Поріг спрацювання, частка видимості елемента (від 0 до 1) |
Приклад з налаштуваннями:
const observer = new IntersectionObserver(callback, {
root: null, // вікно браузера
rootMargin: '0px 0px -100px 0px', // спрацює трохи раніше
threshold: 0.5 // коли видно 50% елемента
});Об'єкт entry у колбеку
Кожен елемент, за яким стежить спостерігач, представлений об'єктом IntersectionObserverEntry:
entry.isIntersecting; // true / false
entry.intersectionRatio; // частка видимої частини (від 0 до 1)
entry.boundingClientRect; // положення елемента
entry.rootBounds; // межі області видимості
entry.target; // сам елементКолбек отримує саме масив таких записів, бо один спостерігач може стежити за сотнями елементів, і за один тік браузер віддає всі зміни разом.
Практичні приклади
Ліниве з'явлення елементів:
const items = document.querySelectorAll('.fade');
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
});
items.forEach((item) => observer.observe(item));.fade {
opacity: 0;
transform: translateY(20px);
transition: 0.5s;
}
.fade.visible {
opacity: 1;
transform: translateY(0);
}Елементи плавно «випливають», коли потрапляють на екран.
Лінива загрузка зображень:
<img data-src="photo.jpg" alt="photo" width="400" height="300" />const images = document.querySelectorAll('img[data-src]');
const observer = new IntersectionObserver((entries, obs) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
obs.unobserve(img); // перестаємо стежити за цією картинкою
}
});
});
images.forEach((img) => observer.observe(img));Тепер зображення завантажуються тільки тоді, коли стають видимими, а це економить трафік і пришвидшує завантаження сайту.
Переваги над scroll і керування спостереженням
| Звичайний scroll | IntersectionObserver |
|---|---|
| Викликає подію на кожному пікселі прокрутки | Працює асинхронно та ефективно |
Треба вручну рахувати getBoundingClientRect() | Усе рахує сам |
| Навантаження на CPU | Майже нульове навантаження |
| Незручно з великою кількістю елементів | Один спостерігач стежить за сотнями елементів |
Щоб перестати стежити за конкретним елементом:
observer.unobserve(target);Щоб повністю вимкнути спостерігач:
observer.disconnect();Підсумок:
| Що робить | IntersectionObserver |
|---|---|
| Відстежує | Коли елемент входить в область видимості або виходить з неї |
| Замінює | onscroll і getBoundingClientRect() |
| Використовується для | Лінивого завантаження, анімацій при скролі, аналітики переглядів |
| Продуктивність | Дуже висока, розрахунки робить браузер, а не ваш JS-код |
Запам'ятайте просто:
IntersectionObserverце «спостерігач за видимістю». Він повідомляє, коли елемент з'явився на екрані, і робить це ефективно, без зайвих подійscroll.
Типові помилки
- Забувати викликати
observer.observe(element). Створений спостерігач сам по собі ні за чим не стежить. - Чекати, що колбек спрацює лише при появі елемента. Він викликається і при виході з видимості, тому перевірка
entry.isIntersectingобов'язкова. - Не викликати
unobserve()після одноразової дії (наприклад підвантаження картинки) і виконувати роботу знову при кожному перетині. - Плутати
threshold: 0іthreshold: 1: перший спрацьовує на першому пікселі елемента, другий лише коли видно весь елемент. - Задавати
rootMarginу відсотках там, де потрібні пікселі, або взагалі без одиниць. Значення без одиниць браузер відхилить. - Використовувати
rootз елементом, який не є предком цілі. Спостереження тоді не працює так, як очікується. - Не відключати спостерігач при знищенні компонента (
disconnect()), через що лишаються витоки пам'яті і колбеки для вже видалених вузлів.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.