Vertrauen ist gut, Beobachtung ist besser: Intersection Observer v2

Mit Intersection Observer v2 können Sie nicht nur Kreuzungen an sich beobachten, sondern auch erkennen, ob das sich überschneidende Element zum Zeitpunkt der Kreuzung sichtbar war.

Intersection Observer v1 ist eine der APIs, die wahrscheinlich allgemein beliebt sind. Da sie jetzt auch von Safari unterstützt wird, ist sie endlich in allen gängigen Browsern einsetzbar. Zur schnellen Auffrischung der API empfehlen wir Ihnen, sich den Supercharged Microtip von Surma auf Intersection Observer v1 anzusehen, der unten eingebettet ist. Außerdem können Sie den ausführlichen Artikel von Surma lesen. Intersection Observer v1 wurde bereits für eine Vielzahl von Anwendungsfällen verwendet, beispielsweise das Lazy Loading von Bildern und Videos, die Benachrichtigung, wenn Elemente position: sticky erreichen, das Auslösen von Analyseereignissen und vieles mehr.

Ausführliche Informationen finden Sie in der Intersection Observer-Dokumentation auf MDN. Zur Erinnerung: Die Intersection Observer v1 API sieht im einfachsten Fall 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'));

Herausforderungen bei Intersection Observer v1

Intersection Observer v1 ist eine tolle Sache, aber sie ist nicht perfekt. Es gibt einige Fälle, in denen die API nicht ausreichend unterstützt wird. Sehen wir uns das genauer an. Mit der Intersection Observer v1 API können Sie zwar erkennen, wenn ein Element in den Darstellungsbereich des Fensters gescrollt wird, nicht jedoch, ob das Element von anderen Seiteninhalten verdeckt wird (wenn es verdeckt ist) oder ob die visuelle Darstellung des Elements durch visuelle Effekte wie transform, opacity, filter usw. verändert wurde, die es effektiv unsichtbar machen können.

Für ein Element im übergeordneten Dokument können diese Informationen ermittelt werden, indem das DOM über JavaScript analysiert wird, z. B. über DocumentOrShadowRoot.elementFromPoint(), und dann weitere Daten ausgewertet werden. Im Gegensatz dazu können dieselben Informationen nicht abgerufen werden, wenn sich das betreffende Element in einem Drittanbieter-iFrame befindet.

Warum ist Sichtbarkeit so wichtig?

Leider ist das Internet ein Ort, an dem böswillige Akteure mit schlechteren Absichten angezogen werden. Beispielsweise könnte ein zwielichtiger Publisher, der Pay-per-Click-Anzeigen auf einer Content-Website schaltet, Anreize schaffen, Nutzer dazu zu verleiten, auf seine Anzeigen zu klicken, um die Auszahlung des Publishers zu erhöhen. Dies sollte zumindest kurzzeitig so lange dauern, bis das Werbenetzwerk die Anzeigen erkennt. Solche Anzeigen werden normalerweise in iFrames ausgeliefert. Wenn der Publisher Nutzer dazu bewegen möchte, auf solche Anzeigen zu klicken, kann er die Anzeigen-iFrames vollständig transparent gestalten, indem er die CSS-Regel iframe { opacity: 0; } anwendet und die iFrames über ein attraktives Objekt legt, z. B. ein niedliches Katzenvideo, auf das der Nutzer tatsächlich klicken möchte. Dies wird als Clickjacking bezeichnet. Sie können einen solchen Clickjacking-Angriff im oberen Abschnitt dieser Demo sehen. Versuchen Sie, das Katzenvideo zu „anschauen“ und den „Trickmodus“ zu aktivieren. Sie werden feststellen, dass die Anzeige im iFrame "glaubt", dass sie rechtmäßige Klicks erhalten hat, auch wenn sie beim Klicken (absichtlich) vollkommen transparent war.

Nutzer werden zum Anklicken einer Anzeige verleitet, indem Sie sie transparent gestalten und sie über eine attraktive Anzeige legen.

Wie behebt Intersection Observer v2 dieses Problem?

Mit Intersection Observer v2 wird das Konzept eingeführt, die tatsächliche „Sichtbarkeit“ eines Zielelements zu verfolgen, wie es von einem Menschen definiert werden würde. Wenn Sie eine Option im IntersectionObserver-Konstruktor festlegen, enthalten die sich überschneidenden IntersectionObserverEntry-Instanzen dann ein neues boolesches Feld mit dem Namen isVisible. Ein true-Wert für isVisible ist eine starke Garantie von der zugrunde liegenden Implementierung, dass das Zielelement vollständig nicht von anderen Inhalten verdeckt wird und keine visuellen Effekte angewendet wurden, die die Darstellung auf dem Bildschirm verändern oder verzerren könnten. Im Gegensatz dazu bedeutet der Wert false, dass die Implementierung diese Garantie nicht garantieren kann.

Ein wichtiges Detail der spec ist, dass die Implementierung falsch negative Ergebnisse melden darf. Das heißt, isVisible wird auf false gesetzt, auch wenn das Zielelement vollständig sichtbar und unverändert ist. Aus Leistungs- und anderen Gründen arbeiten Browser nur mit Begrenzungsrahmen und geradlinige Geometrie. Bei Änderungen wie border-radius wird nicht versucht, pixelgenaue Ergebnisse zu erzielen.

Falsch positive Ergebnisse sind unter keinen Umständen zulässig. Das heißt, isVisible wird auf true gesetzt, wenn das Zielelement nicht vollständig sichtbar und unverändert ist.

Wie sieht der neue Code in der Praxis aus?

Der IntersectionObserver-Konstruktor verwendet jetzt zwei zusätzliche Konfigurationsattribute: delay und trackVisibility. Die 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 Beobachter Änderungen an der Sichtbarkeit eines Ziels verfolgt.

Wichtig: Wenn trackVisibility den Wert true hat, muss delay mindestens 100 betragen (d. h. nicht mehr als eine Benachrichtigung alle 100 ms). Wie bereits erwähnt, ist die Berechnung teuer. Diese Anforderung ist eine Vorsichtsmaßnahme, um Leistungseinbußen und Akkuverbrauch zu vermeiden. Der verantwortliche Entwickler verwendet für die Verzögerung den höchsten tolerierbaren Wert.

Gemäß der aktuellen spec wird die Sichtbarkeit so berechnet:

  • Wenn das Attribut trackVisibility des Beobachters den Wert false hat, gilt das Ziel als sichtbar. Dies entspricht dem aktuellen Verhalten von V1.

  • Wenn das Ziel eine andere effektive Transformationsmatrix als eine 2D-Verschiebung oder proportionale 2D-Upskalierung hat, gilt das Ziel als unsichtbar.

  • Wenn das Ziel oder ein Element in der enthaltenden Blockkette eine andere effektive Deckkraft als 1,0 hat, gilt das Ziel als unsichtbar.

  • Wenn auf das Ziel oder ein Element in der enthaltenden Blockkette Filter angewendet wurden, gilt das Ziel als unsichtbar.

  • Wenn die Implementierung nicht garantieren kann, dass das Ziel vollständig durch andere Seiteninhalte verdeckt wird, gilt das Ziel als unsichtbar.

Das bedeutet, dass die aktuellen Implementierungen bei der Gewährleistung der Sichtbarkeit ziemlich konservativ sind. Wenn Sie beispielsweise einen fast unbemerkten Graustufenfilter wie filter: grayscale(0.01%) anwenden oder mit opacity: 0.99 eine fast unsichtbare Transparenz festlegen, wird das Element unsichtbar.

Im Folgenden finden Sie ein kurzes Codebeispiel, das die neuen API-Funktionen veranschaulicht. Im zweiten Abschnitt der Demo können Sie die Klick-Tracking-Logik in Aktion sehen (aber mal sehen Sie sich das Welpenvideo an). Aktiviere den „Trickmodus“, um dich sofort in einen zwielichtigen Publisher zu verwandeln und zu sehen, wie Intersection Observer v2 verhindert, dass unzulässige Anzeigenklicks erfasst werden. Dieses Mal ist Intersection Observer v2 verfügbar. 🎉

Intersection Observer Version 2 verhindert unbeabsichtigte Klicks auf eine Anzeige.

<!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 Intersection Observer v2, falling back 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'));

Danksagungen

Vielen Dank an Simeon Vincent, Yoav Weiss und Mathias Bynens für die Lesen dieses Artikels sowie für Stefan Zager für die Überprüfung und Implementierung der Funktion in Chrome. Hero-Image von Sergey Semin auf Unsplash