Zaufanie jest dobre, ale obserwacja jest lepsza: Intersection Observer v2

Browser Support

  • Chrome: 51.
  • Edge: 15.
  • Firefox: 55.
  • Safari: 12.1.

Source

Intersection Observer to jeden z interfejsów API, który jest prawdopodobnie powszechnie lubiany i można go używać we wszystkich głównych przeglądarkach. Deweloperzy używają tego interfejsu API w wielu przypadkach, m.in. do leniwej wczytywania obrazów i filmów, wysyłania powiadomień, gdy elementy osiągną position: sticky, wywoływania zdarzeń analitycznych i wielu innych.

W najprostszej postaci interfejs Intersection Observer API w wersji 1 wygląda tak:

const onIntersection = (entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      console.log(entry);
    }
  }
};

const observer = new IntersectionObserver(onIntersection);
observer.observe(document.querySelector('#some-target'));

Problemy z widocznością w Intersection Observer API w wersji 1

Interfejs Intersection Observer API w wersji 1 umożliwia sprawdzenie, kiedy element zostanie przewinięty do obszaru widocznego w oknie. Nie możesz jednak określić, czy ten element jest zasłonięty przez inne treści na stronie (tzw. okluzja) ani czy element został zmodyfikowany przez CSS, np. za pomocą właściwości transform, opacity lub filter, co może spowodować, że element będzie niewidoczny.

W przypadku elementu w dokumencie najwyższego poziomu te informacje można określić, analizując DOM za pomocą JavaScriptu, np. za pomocą DocumentOrShadowRoot.elementFromPoint(). Natomiast tych samych informacji nie można uzyskać, jeśli dany element znajduje się w ramce iframe pochodzącej od innej firmy.

Dlaczego widoczność jest ważna?

Niestety w internecie działają nieuczciwe osoby. Na przykład nieuczciwy wydawca może używać w witrynie reklam typu „płatność za kliknięcie”. Mogą oni nakłaniać użytkowników do klikania tych reklam, aby zarabiać więcej pieniędzy, przynajmniej do czasu, gdy sieć reklamowa odkryje ich oszustwo. Takie reklamy są zwykle wyświetlane w ramkach iframe.

Aby oszukać użytkowników, wydawca może sprawić, że ramki iframe reklamy będą całkowicie przezroczyste za pomocą kodu CSS: iframe { opacity: 0; }. Następnie mogą umieszczać te przezroczyste ramki iframe nad atrakcyjnymi treściami, np. filmem z uroczym kotem, które użytkownicy chcą kliknąć. Jest to tzw. clickjacking.

Przykład ataku typu clickjacking możesz zobaczyć w górnej części naszej wersji demonstracyjnej. Spróbuj „obejrzeć” film z kotem i włącz tryb sztuczek. Reklama w elemencie iframe rejestruje kliknięcia jako prawidłowe, nawet jeśli klikniesz ją (przypadkowo), gdy element iframe jest przezroczysty.

Nakłanianie użytkownika do kliknięcia reklamy przez nadanie jej przezroczystego stylu i nałożenie jej na atrakcyjny element.

Ulepszenia w Intersection Observer v2

Interfejs Intersection Observer v2 może śledzić „widoczność” elementu w sposób, w jaki definiuje ją człowiek. Jeśli ustawisz opcję w IntersectionObserverkonstruktorze, wynikowe instancje IntersectionObserverEntry będą zawierać nowe pole logiczne o nazwie isVisible. Gdy wartość isVisible wynosi true, przeglądarka dba o to, aby element nie był całkowicie zasłonięty przez inne treści i nie miał efektów wizualnych, które ukrywają lub zmieniają jego wyświetlanie. Jeśli wartość parametru isVisible to false, przeglądarka nie może tego zagwarantować.

Specyfikacja dopuszcza fałszywie negatywne wyniki: isVisible może być false, nawet jeśli element jest rzeczywiście widoczny i niezmieniony. Aby zwiększyć wydajność, przeglądarki używają prostszych obliczeń, takich jak ramki ograniczające i kształty prostokątne, i nie sprawdzają każdego piksela pod kątem złożonych szczegółów, takich jak border-radius.

Wyniki fałszywie pozytywne są jednak w każdej sytuacji niedozwolone. Oznacza to, że isVisible nie będzie true, jeśli element nie jest w pełni widoczny i nie został zmodyfikowany.

Zastosuj te zmiany

Konstruktor IntersectionObserver przyjmuje teraz 2 dodatkowe właściwości konfiguracji:

  • delay to liczba wskazująca minimalne opóźnienie w milisekundach między powiadomieniami od obserwatora dla danego celu.
  • trackVisibility to wartość logiczna wskazująca, czy obserwator będzie śledzić zmiany widoczności elementu docelowego.

Gdy wartość trackVisibility wynosi true, wartość delay musi być ustawiona na 100 lub wyższą (czyli nie więcej niż 1 powiadomienie co 100 ms). Obliczanie widoczności jest kosztowne, dlatego jest to środek ostrożności zapobiegający pogorszeniu wydajności i zużyciu baterii. Odpowiedzialni deweloperzy powinni używać największej dopuszczalnej wartości opóźnienia.

Specyfikacja oblicza widoczność. Podobnie jak w przypadku wersji 1, gdy atrybut trackVisibilityobserwatora ma wartość false, element docelowy jest uznawany za widoczny.

W wersji 2 element docelowy jest uznawany za niewidoczny, jeśli:

  • Ma efektywną macierz przekształcenia inną niż przekształcenie 2D lub proporcjonalne powiększenie 2D.

  • Element docelowy lub dowolny element w jego łańcuchu bloków zawierającym ma efektywną nieprzezroczystość mniejszą niż 1,0.

  • Do elementu docelowego lub dowolnego elementu w jego łańcuchu bloków zawierających zastosowano filtry.

  • Jeśli implementacja nie może zagwarantować, że element docelowy nie jest całkowicie zasłonięty przez inne treści na stronie.

Oznacza to, że obecne wdrożenia są dość ostrożne w gwarantowaniu widoczności. Na przykład zastosowanie prawie niezauważalnego filtra w odcieniach szarości (filter: grayscale(0.01%)) lub ustawienie najmniejszej przezroczystości (opacity: 0.99) spowoduje, że element będzie niewidoczny.

Oto przykładowy kod, który ilustruje nowe funkcje interfejsu API. Logikę śledzenia kliknięć możesz zobaczyć w drugiej części wersji demonstracyjnej. Spróbuj „obejrzeć” film z szczeniakiem. Aktywuj tryb oszustwa, aby przekształcić się w osobę działającą na szkodę i sprawdzić, jak interfejs Intersection Observer v2 zapobiega śledzeniu nieprawidłowych kliknięć reklam. Intersection Observer v2 chroni nas.

Interfejs Intersection Observer w wersji 2 zapobiega niezamierzonemu kliknięciu reklamy.

<!DOCTYPE html>
<!-- This is the ad running in the iframe -->
<button id="callToActionButton">Buy now!</button>
// This is code running in the iframe.

// The iframe must be visible for at least 800ms prior to an input event
// for the input event to be considered valid.
const minimumVisibleDuration = 800;

// Keep track of when the button transitioned to a visible state.
let visibleSince = 0;

const button = document.querySelector('#callToActionButton');
button.addEventListener('click', (event) => {
  if ((visibleSince > 0) &&
      (performance.now() - visibleSince >= minimumVisibleDuration)) {
    trackAdClick();
  } else {
    rejectAdClick();
  }
});

const observer = new IntersectionObserver((changes) => {
  for (const change of changes) {
    // ⚠️ Feature detection
    if (typeof change.isVisible === 'undefined') {
      // The browser doesn't support v2, fallback to v1 behavior.
      change.isVisible = true;
    }
    if (change.isIntersecting && change.isVisible) {
      visibleSince = change.time;
    } else {
      visibleSince = 0;
    }
  }
}, {
  threshold: [1.0],
  // 🆕 Track the actual visibility of the element
  trackVisibility: true,
  // 🆕 Set a minimum delay between notifications
  delay: 100
}));

// Require that the entire iframe be visible.
observer.observe(document.querySelector('#ad'));

Dodatkowe materiały

Podziękowania

Dziękujemy za sprawdzenie Simeonowi Vincentowi, Yoavowi WeissowiMathiasowi Bynensowi, a także Stefanowi Zagerowi za sprawdzenie i wdrożenie tej funkcji w Chrome.