IntersectionObserver

Obserwuje, czy element jest widoczny w oknie albo w przewijanym kontenerze, i wywołuje funkcję, gdy to się zmienia.

Zwraca
Nowy obiekt IntersectionObserver.
Na tej stronie

Przykład

#
JavaScript
const feed = document.querySelector('#feed')
const lessons = feed.querySelectorAll('p')

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    const state = entry.isIntersecting ? 'widoczna' : 'ukryta'
    console.log(`${entry.target.textContent}: ${state}`)
  }
}, { root: feed })

lessons.forEach((lesson) => observer.observe(lesson))
console.log(`Obserwuję ${lessons.length} lekcji`)
Konsola
Obserwuję 5 lekcji
Zmienne: widoczna
Warunki: widoczna
Pętle: widoczna
Funkcje: ukryta
Tablice: ukryta

Pierwszy wpis pochodzi z kodu synchronicznego, a stan lekcji przychodzi później, w funkcji zwrotnej. „Pętle” widać tylko częściowo, ale przy domyślnym threshold: 0 wystarczy choćby jeden piksel. Przewiń listę w podglądzie, a w konsoli pojawią się kolejne zmiany.

Definicja i zastosowanie

#

IntersectionObserver informuje, kiedy element pojawia się w widocznym obszarze albo z niego znika. Zamiast sprawdzać położenie elementów przy każdym przewinięciu strony, tworzysz obserwatora z funkcją zwrotną, a przeglądarka sama wywołuje ją we właściwym momencie. To podstawa leniwego wczytywania, nieskończonych list, animacji uruchamianych przy przewijaniu i liczenia wyświetleń.

Funkcja zwrotna dostaje tablicę obiektów IntersectionObserverEntry. Najważniejsze pola to target (obserwowany element), isIntersecting (czy element jest widoczny) i intersectionRatio (jaka część elementu jest widoczna, od 0 do 1). Pierwsze wywołanie następuje zaraz po observe(), więc od razu poznajesz stan początkowy.

Opcje podajesz w drugim argumencie konstruktora. root to element, względem którego liczona jest widoczność (domyślnie okno przeglądarki). rootMargin rozszerza lub zwęża ten obszar, np. "200px" pozwala zacząć wczytywanie, zanim element pojawi się na ekranie. threshold określa, przy jakiej części widocznej powierzchni wywołać funkcję, np. 0.5 albo [0, 0.5, 1].

Metoda observe() dodaje element do obserwacji, unobserve() go usuwa, a disconnect() kończy całą obserwację. Funkcja zwrotna działa asynchronicznie, więc jej wpisy w konsoli pojawią się po kodzie synchronicznym.

Składnia

#
Składnia
new IntersectionObserver(callback)
new IntersectionObserver(callback, options)

observer.observe(element)
observer.unobserve(element)
observer.disconnect()

Parametry

#
  • callback

    function (entries, observer)

    Funkcja wywoływana przy zmianie widoczności. Dostaje tablicę wpisów i samego obserwatora.
  • options

    opcjonalny obiekt

    root (element albo null dla okna), rootMargin (margines zapisany jak w CSS, np. "0px 0px 200px 0px") i threshold (liczba albo tablica liczb od 0 do 1).

Więcej przykładów

#
Jaka część elementu jest widoczna
JavaScript
const viewport = document.querySelector('#viewport')

const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    const percent = Math.round(entry.intersectionRatio * 100)
    console.log(`${entry.target.textContent}: widać ${percent}%`)
  }
}, { root: viewport, threshold: [0, 0.25, 0.5, 0.75, 1] })

viewport.querySelectorAll('.card').forEach((card) => observer.observe(card))
Konsola
HTML: widać 100%
CSS: widać 25%

Druga karta wystaje poza kontener, więc widać tylko jej ćwierć. Przewiń kontener w podglądzie, a obserwator odezwie się za każdym razem, gdy widoczna część przekroczy jeden z progów z threshold.

Nieskończona lista zadań
JavaScript
const list = document.querySelector('#list')
const sentinel = document.querySelector('#sentinel')
let page = 0

const observer = new IntersectionObserver(([entry]) => {
  if (!entry.isIntersecting) return
  page += 1

  for (let i = 1; i <= 5; i++) {
    const item = document.createElement('li')
    item.textContent = `Zadanie ${(page - 1) * 5 + i}`
    sentinel.before(item)
  }
  console.log(`Wczytano stronę ${page}`)

  if (page === 3) {
    observer.disconnect()
    sentinel.textContent = 'To już wszystkie zadania'
  }
}, { root: list })

observer.observe(sentinel)
Konsola
Wczytano stronę 1

Na końcu listy stoi element-strażnik. Gdy staje się widoczny, dokładamy kolejne zadania przed nim. Przewiń listę do końca w podglądzie, żeby wczytać następne strony. Po trzeciej obserwator się rozłącza.

Leniwe wczytywanie obrazków
JavaScript
const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (!entry.isIntersecting) continue
    const img = entry.target
    img.src = img.dataset.src
    observer.unobserve(img)
  }
}, { rootMargin: '200px' })

document.querySelectorAll('img[data-src]').forEach((img) => observer.observe(img))

Bez opcji root widoczność jest liczona względem okna przeglądarki. Obrazek dostaje prawdziwy adres dopiero wtedy, gdy znajdzie się 200 px od ekranu, a potem przestaje być obserwowany. W prostych przypadkach to samo daje atrybut HTML loading="lazy".

Obsługa przeglądarek

#

Szeroko dostępne · od 2019 roku

Działa we wszystkich nowoczesnych przeglądarkach, także na telefonach. Możesz używać bez obaw.

  • Chrome
  • Edge
  • Firefox
  • Safari

Dobre praktyki

#
  • Po jednorazowej akcji, np. wczytaniu obrazka, wywołaj observer.unobserve(element). Obserwator nie będzie bez potrzeby śledził elementu dalej.
  • Jeden obserwator może śledzić wiele elementów. Nie twórz osobnego obserwatora dla każdej pozycji listy.
  • Do samego leniwego wczytywania obrazków często wystarczy atrybut loading="lazy" w HTML. Obserwatora użyj, gdy potrzebujesz własnej logiki.

Powiązane hasła

#

Widzisz błąd albo brakuje przykładu? Napisz do nas.