Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial
  • Lezione 106 di 112
  • livello avanzato
Indice lezioni

Intersection Observer: gestire la visibilità degli elementi

Utilizzare le Intersection Observer API di JavaScript per gestire la modifica della visibilità degli elementi HTML di una pagina web.
Utilizzare le Intersection Observer API di JavaScript per gestire la modifica della visibilità degli elementi HTML di una pagina web.
Link copiato negli appunti

Durante la fase di sviluppo di un sito o di un'applicazione web, è facile avere la necessità di calcolare la visibilità di un particolare elemento del DOM: infinite scroll, lazy loading, adv sono solo alcune delle casistiche in cui può essere utile capire se un elemento è visibile o meno.

Per risolvere questo problema è possibile sfruttare il metodo getBoundingClientRect che ci fornisce indicazioni sulla posizione di un determinato elemento rispetto al viewport. Un calcolo ad intervalli regolari o ad ogni evento di scroll ci può fornire informazioni sulla visibilità di un determinato oggetto. Questo approccio è, però, poco performante e può degradare le prestazioni dell'applicazione.

Da pochi mesi, nei browser moderni, è disponibile una nuova API che ci fornisce una metodologia per gestire in maniera ottimale il problema: l'Intersection Observer API fornisce un metodo per verificare in modo asincrono le sovrapposizioni fra un elemento ed un elemento padre, o direttamente in relazione al viewport. Tramite l'Intersection Observer API è possibile definire una funzione di callback che viene eseguita ogni volta che un elemento entra, esce o modifica la percentuale di sovrapposizione con un altro elemento (o con il viewport).

Scriviamo un po' di codice per capire come funziona l'API. Per prima cosa, creaiamo una nuova istanza dell'Intersection Observer:

var options = {
			root: document.querySelector("#parent"),
			rootMargin: "100px",
			threshold: 1.0
		}
		var observer = new IntersectionObserver(callback, options);

L'observer richiede due parametri: una funzione di callback e un oggetto che rappresenta le opzioni con cui applicare la callback. Nelle opzioni, possiamo specificare i seguenti parametri:

  • root: è il contenitore che il browser utilizza per verificare la visibilità di un altro elemento. Se non specificato, il browser utilizza il viewport. Se specificato, esso deve essere obbligatoriamente padre dell'elemento su cui andiamo ad agganciare l'observer;
  • rootMargin: sono i margini dell'elemento root. Permettono di modificare le dimensioni del box per alterare il calcolo della visibilità dell'elemento;
  • threshold: indica la percentuale di visibilità superata la quale viene scatenata la funzione di callback. È possibile specificare un singolo valore o un array di valori.

Ora dobbiamo agganciare l'observer ad un elemento del DOM.

var target = document.querySelector("#element");
		observer.observe(target);

Possiamo ora definire la funzione di callback che viene eseguita dal browser quando tutte le condizioni definite precedentemente sono soddisfatte:

var callback = function(entries, observer) {
			entries.forEach(entry => {
				// Ogni entry descrive un cambio di intersezione
				// di uno degli elementi che stiamo osservando.
				// Per ognuno di essi possiamo valutare le seguenti proprietà
				//   entry.boundingClientRect
				//   entry.intersectionRatio
				//   entry.intersectionRect
				//   entry.isIntersecting
				//   entry.rootBounds
				//   entry.target
				//   entry.time
			});
		};

Per capire meglio il funzionamento dell'API, realizziamo un esempio concreto: creiamo un semplice carosello di immagini. L'obiettivo è quello di caricare le immagini solamente nel momento in cui una scheda del carosello diventa visibile. L'esempio completo è visibile a questo link, mentre il codice è reperibile su Github.

Il primo step consiste nella creazione del codice HTML:

<div class="carousel" id="carousel">
			<div class="carousel-item" data-src="https://picsum.photos/800/600?image=1"></div>
			<div class="carousel-item" data-src="https://picsum.photos/800/600?image=2"></div>
			...
			...
		</div>

L'elemento carousel contiene tutti gli elementi del carosello: ogni elemento è caratterizzato da una proprietà data-src che ha come valore l'URL dell'immagine da caricare in maniera lazy.

Aggiungiamo due link per la gestione delle frecce del carosello:

<a href="#" id="nav-prev" class="nav nav--prev">&lt;</a>
		<a href="#" id="nav-next" class="nav nav--next">&gt;</a>

Ad ogni click, andiamo a modificare la posizione dell'elemento carousel agendo sulla proprietà transform: translateX().

Tralasciando il codice per la gestione del carosello, concentriamoci su quello necessario per gestire l'Intersection Observer API. Creiamo un file app.js ed al suo interno definiamo una nuova istanza dell'observer. Nel nostro carosello, ogni scheda occupa tutto lo schermo: per questo motivo possiamo definire come unica opzione il threshold e tralasciare le proprietà root e rootMargin. Agganciamo quindi all'observer tutti gli elementi del carosello tramite il metodo forEach:

const options = {
			threshold: [0, 0.5, 1]
		}
		const observer = new IntersectionObserver(callback, options);
		const targets = document.querySelectorAll(".carousel-item");
		targets.forEach(target => observer.observe(target));

Fatto questo, definiamo la funzione di callback:

entries.forEach(entry => {
			// calcolo la percentuale di sovrapposizione
			const ratio = entry.intersectionRatio;
			const element = entry.target;
			// se l'elemento è visibile
			if(ratio > 0){
				// rimuovo l'elemento dall'observer
				observer.unobserve(element);
				// recupero l'immagine e la carico a video
				const src = element.dataset.src
				fetchImage(src).then(() => {
					applyBg(element, src);
				});
			}
		});

Ogni volta che un elemento diventa visibile, esso viene rimosso dagli elementi osservati dall'observer. Successivamente, viene recuperata la proprietà data-src, caricata e mostrata l'immagine a video. Per queste ultime operazioni possiamo utilizzare le seguenti funzioni:

function fetchImage(url) {
			return new Promise((resolve, reject) => {
				const image = new Image();
				image.src = url;
				image.onload = resolve;
				image.onerror = reject;
			});
		}
		function applyBg(el, src){
			el.classList.add("loaded");
			el.style.backgroundImage = `url("${src}")`;
		}

L'esempio è molt semplice, ma mostra chiaramente quanto sia semplice ed efficace l'utilizzo dell'Intersection Observer API. Con poche semplici righe di codice abbiamo la possibilità di eseguire una funzione nel momento in cui un elemento diventa visibile all'interno del viewport. Unico piccolo difetto di questa nuova tecnologia è il supporto dei browser: funziona infatti solo sulle ultime versioni di Firefox, Chrome ed Edge, ma non su IE e Safari. Per sopperire a questa mancanza, è però possibile utilizzare un polyfill.

Ti consigliamo anche