La fiducia è buona, l'osservazione è meglio: Intersection Exampler v2

L'osservatore di intersezione v2 aggiunge la capacità non solo di osservare le intersezioni in sé, ma anche di rilevare se l'elemento che si interseca era visibile al momento dell'intersezione.

Intersection Observationr v1 è una di queste API probabilmente amata da tutti e, ora che Safari la supporta, è finalmente utilizzabile a livello globale in tutti i principali browser. Per un rapido ripasso dell'API, ti consiglio di guardare il video Supercharged Microtip di Surma su Intersection Observationr v1 incorporato di seguito. Puoi anche leggere l'articolo approfondito di Surma. Gli utenti hanno utilizzato Intersection Observationr v1 per un'ampia gamma di casi d'uso, come il caricamento lento di immagini e video, la ricezione di notifiche quando gli elementi raggiungono position: sticky, l'attivazione di eventi di analisi e molti altri.

Per i dettagli completi, consulta la documentazione di Intersection Observationr su MDN, ma un breve promemoria, questo è l'aspetto dell'API Intersection Observationr v1 nel caso più semplice:

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

Quali sono le sfide con Intersection Observationr v1?

Per essere chiari, Intersection Observationr v1 è ottimo, ma non lo è. Ci sono alcuni casi particolari in cui l'API non è sufficiente. Diamo un'occhiata in dettaglio. L'API Intersection Observationr v1 può indicare quando viene fatto scorrere un elemento nell'area visibile della finestra, ma non indica se l'elemento è coperto da altri contenuti della pagina (ovvero quando è occulto) o se la visualizzazione dell'elemento è stata modificata da effetti visivi come transform, opacity, filter e così via, il che può effettivamente renderlo invisibile.

Per un elemento del documento di primo livello, queste informazioni possono essere determinate analizzando il DOM tramite JavaScript, ad esempio tramite DocumentOrShadowRoot.elementFromPoint(), per poi andare più a fondo. Al contrario, non è possibile ottenere le stesse informazioni se l'elemento in questione si trova in un iframe di terze parti.

Perché la visibilità effettiva è così importante?

Internet, sfortunatamente, è un luogo che attira malintenzionati con intenzioni peggiori. Ad esempio, un publisher non attendibile che pubblica annunci pay-per-click su un sito di contenuti potrebbe essere incentivato a fare in modo che gli utenti facciano clic sui suoi annunci per aumentare il pagamento pubblicitario del publisher (almeno per un breve periodo, finché la rete pubblicitaria non li rileva). Generalmente, questi annunci vengono pubblicati in iframe. Se il publisher volesse incoraggiare gli utenti a fare clic su questi annunci, potrebbe rendere gli iframe degli annunci completamente trasparenti applicando una regola CSS iframe { opacity: 0; } e sovrapponendo gli iframe su qualcosa di interessante, come un simpatico video di gatti su cui gli utenti vorrebbero fare clic. Questo processo si chiama clickjacking. Puoi vedere questo attacco clickjacking in azione nella sezione superiore di questa demo (prova a "guardare" il video dei gatti e attiva la "modalità trucco"). Noterai che l'annuncio nell'iframe "ritiene" di aver ricevuto clic legittimi, anche se è stato completamente trasparente quando hai fatto clic (fingendo volontariamente) di averlo fatto.

Indurre con l'inganno un utente a fare clic su un annuncio applicandone uno trasparente e sovrapponendolo a qualcosa di interessante.

In che modo Intersection Observationr v2 risolve questo problema?

Intersection Observationr v2 introduce il concetto di monitoraggio della "visibilità" effettiva di un elemento target come lo definirebbe un essere umano. Se imposti un'opzione nel costruttore IntersectionObserver, l'intersezione delle istanze IntersectionObserverEntry conterrà un nuovo campo booleano denominato isVisible. Un valore true per isVisible è una solida garanzia dell'implementazione sottostante che l'elemento target non è completamente visibile da altri contenuti e non ha effetti visivi che ne alterano o distorcono la visualizzazione sullo schermo. Al contrario, un valore false indica che l'implementazione non può offrire questa garanzia.

Un dettaglio importante della spec è che l'implementazione può segnalare falsi negativi (ovvero, impostare isVisible su false anche quando l'elemento target è completamente visibile e non modificato). Per motivi legati alle prestazioni o per altri motivi, i browser si limitano a utilizzare riquadri di delimitazione e geometria rettilinea; non cercano di ottenere risultati perfetti per modifiche come border-radius.

Detto questo, i falsi positivi non sono consentiti in nessuna circostanza (ovvero, impostare isVisible su true quando l'elemento target non è completamente visibile e non è stato modificato).

Come si presenta il nuovo codice in pratica?

Il costruttore IntersectionObserver ora richiede due proprietà di configurazione aggiuntive: delay e trackVisibility. delay è un numero che indica il ritardo minimo in millisecondi tra le notifiche dell'osservatore per un determinato target. trackVisibility è un valore booleano che indica se l'osservatore monitorerà le modifiche relative alla visibilità di un target.

È importante notare che quando trackVisibility è true, delay deve essere almeno 100 (ossia non più di una notifica ogni 100 ms). Come indicato in precedenza, il calcolo della visibilità è costoso e questo requisito è una precauzione contro il degrado delle prestazioni e il consumo della batteria. Lo sviluppatore responsabile utilizzerà il valore più tollerabile per il ritardo.

In base alla spec attuale, la visibilità viene calcolata come segue:

  • Se l'attributo trackVisibility dell'osservatore è false, il target è considerato visibile. Ciò corrisponde all'attuale comportamento della versione 1.

  • Se il target ha una matrice di trasformazione effettiva diversa dalla traduzione 2D o dall'upscaling proporzionale 2D, il target è considerato invisibile.

  • Se il target, o qualsiasi elemento della sua catena di blocchi contenente, ha un'opacità effettiva diversa da 1,0, il target è considerato invisibile.

  • Se al target o a qualsiasi elemento della sua catena a blocchi contenente sono applicati filtri, il target è considerato invisibile.

  • Se l'implementazione non può garantire che la destinazione non sia completamente esclusa dagli altri contenuti della pagina, il target è considerato invisibile.

Ciò significa che le implementazioni attuali sono piuttosto prudenti e garantiscono la visibilità. Ad esempio, l'applicazione di un filtro a scala di grigi quasi impercettibile come filter: grayscale(0.01%) o l'impostazione di una trasparenza quasi invisibile con opacity: 0.99 renderebbe l'elemento invisibile.

Di seguito è riportato un breve esempio di codice che illustra le nuove funzionalità dell'API. Puoi vedere la logica del monitoraggio dei clic in azione nella seconda sezione della demo. Ora prova a "guardare" il video dei cuccioli. Accertati di attivare di nuovo la "modalità di trucco" per convertirti immediatamente in un publisher non attendibile e scoprire come Intersection Observationr v2 impedisce il monitoraggio dei clic sugli annunci non legittimi. Questa volta ci pensa l'Intersection Observationr v2. 🎉

Intersection Observationr v2 impedisce ai clic involontari su un annuncio.

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

Ringraziamenti

Ringraziamo Simeon Vincent, Yoav Weiss e Mathias Bynens per aver letto questo articolo e Stefan Zager per aver esaminato e per l'implementazione della funzionalità in Chrome. Immagine hero di Sergey Semin su Unsplash.