Vertrauen ist gut, Beobachtung ist besser: Intersection Observer v2

Browser Support

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

Source

Intersection Observer ist eine dieser APIs, die wahrscheinlich überall beliebt sind und in allen wichtigen Browsern verwendet werden können. Entwickler haben diese API für eine Vielzahl von Anwendungsfällen verwendet, darunter Lazy Loading von Bildern und Videos, Benachrichtigungen, wenn Elemente position: sticky erreichen, und Auslösen von Analyseereignissen.

In der einfachsten Form sieht die Intersection Observer v1 API so aus:

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'));

Sichtbarkeitsprobleme in Intersection Observer v1

Mit der Intersection Observer v1 API können Sie ermitteln, wann ein Element in den Darstellungsbereich des Fensters gescrollt wird. Sie können jedoch nicht feststellen, ob das Element von anderen Seiteninhalten verdeckt wird (Verdeckung) oder ob das Element durch CSS-Attribute wie transform, opacity oder filter modifiziert wird, wodurch es möglicherweise unsichtbar ist.

Bei einem Element im Dokument der obersten Ebene können diese Informationen durch Analysieren des DOM mit JavaScript ermittelt werden, z. B. mit DocumentOrShadowRoot.elementFromPoint(). Wenn sich das betreffende Element in einem Drittanbieter-Iframe befindet, können dieselben Informationen nicht abgerufen werden.

Warum ist Sichtbarkeit wichtig?

Leider gibt es im Internet auch böswillige Akteure. Ein unehrlicher Publisher könnte beispielsweise Pay-per-Click-Anzeigen auf einer Website verwenden. Sie könnten Nutzer dazu verleiten, auf diese Anzeigen zu klicken, um mehr Geld zu verdienen, zumindest bis das Werbenetzwerk ihr Vorhaben entdeckt. Solche Anzeigen werden in der Regel in iFrames ausgeliefert.

Um Nutzer zu täuschen, könnte der Publisher die Anzeigen-iFrames mit CSS vollständig transparent machen: iframe { opacity: 0; }. Anschließend konnten sie diese transparenten iFrames über ansprechenden Inhalten wie einem süßen Katzenvideo platzieren, auf die Nutzer klicken möchten. Das nennt man Clickjacking.

Ein Beispiel für einen solchen Clickjacking-Angriff finden Sie oben in unserer Demo. Sie können sich das Katzenvideo auch „ansehen“ und den Trickmodus aktivieren. Die Anzeige im iFrame registriert Klicks als zulässig, auch wenn Sie (unbeabsichtigt) darauf geklickt haben, als der iFrame transparent war.

Nutzer werden dazu verleitet, auf eine Anzeige zu klicken, indem sie transparent gestaltet und über etwas Attraktives gelegt wird.

Verbesserungen bei Intersection Observer v2

Mit Intersection Observer v2 lässt sich die „Sichtbarkeit“ eines Elements so erfassen, wie sie ein Mensch definieren würde. Wenn Sie eine Option im IntersectionObserver-Konstruktor festlegen, enthalten die resultierenden IntersectionObserverEntry-Instanzen ein neues boolesches Feld namens isVisible. Wenn isVisible gleich true ist, sorgt der Browser dafür, dass das Element nicht von anderen Inhalten verdeckt wird und keine visuellen Effekte vorhanden sind, die die Darstellung des Elements verbergen oder ändern. Wenn isVisible false ist, kann der Browser diese Garantie nicht geben.

Die Spezifikation lässt falsch negative Ergebnisse zu: isVisible kann false sein, auch wenn das Element tatsächlich sichtbar und unverändert ist. Aus Leistungsgründen verwenden Browser einfachere Berechnungen, z. B. Begrenzungsrahmen und rechteckige Formen, und prüfen nicht jedes Pixel auf komplexe Details wie border-radius.

Falsch positive Ergebnisse sind jedoch unter keinen Umständen zulässig. Das bedeutet, dass isVisible nicht true ist, wenn das Element nicht vollständig sichtbar und unverändert ist.

Diese Änderungen anwenden

Der Konstruktor IntersectionObserver hat jetzt zwei zusätzliche Konfigurationseigenschaften:

  • delay ist eine Zahl, die die Mindestverzögerung in Millisekunden zwischen Benachrichtigungen des Beobachters für ein bestimmtes Ziel angibt.
  • trackVisibility ist ein boolescher Wert, der angibt, ob der Observer Änderungen an der Sichtbarkeit eines Ziels verfolgt.

Wenn trackVisibility = true, muss delay auf 100 oder einen höheren Wert festgelegt werden (d. h. maximal eine Benachrichtigung alle 100 ms). Da die Berechnung der Sichtbarkeit teuer ist, soll dies eine Vorsichtsmaßnahme gegen Leistungseinbußen und Akkuverbrauch sein. Verantwortungsbewusste Entwickler sollten den größtmöglichen tolerierbaren Wert für die Verzögerung verwenden.

Die Spezifikation berechnet die Sichtbarkeit. Wie bei Version 1 gilt das Ziel als sichtbar, wenn das Attribut trackVisibility des Observers false ist.

In Version 2 gilt das Ziel als unsichtbar, wenn:

  • Es hat eine effektive Transformationsmatrix, die keine 2D-Verschiebung oder proportionale 2D-Hochskalierung ist.

  • Das Ziel oder ein Element in der zugehörigen Blockkette hat eine effektive Deckkraft von weniger als 1,0.

  • Auf das Ziel oder ein beliebiges Element in der zugehörigen Blockkette werden Filter angewendet.

  • Wenn die Implementierung nicht garantieren kann, dass das Ziel nicht durch andere Seiteninhalte verdeckt wird.

Das bedeutet, dass aktuelle Implementierungen ziemlich konservativ sind, wenn es darum geht, Sichtbarkeit zu garantieren. Wenn Sie beispielsweise einen kaum wahrnehmbaren Graustufenfilter (filter: grayscale(0.01%)) anwenden oder die geringste Transparenz (opacity: 0.99) festlegen, wird das Element unsichtbar.

Hier ist ein Codebeispiel, das die neuen API-Funktionen veranschaulicht. Die Logik für das Klick-Tracking in Aktion sehen Sie im zweiten Abschnitt der Demo. Versuchen Sie, das Welpenvideo anzusehen. Aktivieren Sie den Trickmodus, um sich in einen böswilligen Akteur zu verwandeln und zu sehen, wie mit Intersection Observer v2 verhindert wird, dass nicht legitime Anzeigenklicks erfasst werden. Intersection Observer v2 schützt uns.

Intersection Observer v2 verhindert unbeabsichtigte Klicks auf Anzeigen.

<!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'));

Zusätzliche Ressourcen

Danksagungen

Vielen Dank an Simeon Vincent, Yoav Weiss und Mathias Bynens für die Überprüfung sowie an Stefan Zager für die Überprüfung und Implementierung der Funktion in Chrome.