Gli IntersectionObserver ti consentono di sapere quando un elemento osservato entra o esce dalla visualizzazione del browser.
Supponiamo che tu voglia monitorare quando un elemento del DOM entra nell'area visibile. Potresti volerlo fare per caricare le immagini in modalità lazy load al momento giusto o perché devi sapere se l'utente sta effettivamente guardando un determinato banner pubblicitario. Puoi farlo collegando l'evento di scorrimento o utilizzando un timer periodico e chiamando getBoundingClientRect() su quell'elemento.
Tuttavia, questo approccio è estremamente lento, poiché ogni chiamata a getBoundingClientRect() costringe il browser a eseguire nuovamente il layout dell'intera pagina e introduce un notevole jank nel tuo sito web. Le cose diventano quasi impossibili quando sai che il tuo sito viene caricato all'interno di un iframe e vuoi sapere quando l'utente può vedere un elemento. Il modello Single Origin e il browser non ti consentiranno di accedere ai dati della pagina web che contiene l'iframe. Questo è un problema comune, ad esempio, per gli annunci, che vengono spesso caricati utilizzando gli iframe.
Rendere più efficiente questo test di visibilità è lo scopo per cui è stato progettato IntersectionObserver, che è stato implementato in tutti i browser moderni. IntersectionObserver ti consente di sapere quando un elemento osservato entra o esce dall'area visibile del browser.
Come creare un IntersectionObserver
L'API è piuttosto piccola e può essere descritta meglio con un esempio:
const io = new IntersectionObserver(entries => {
console.log(entries);
}, {
/* Using default options. Details below */
});
// Start observing an element
io.observe(element);
// Stop observing an element
// io.unobserve(element);
// Disable entire IntersectionObserver
// io.disconnect();
Se utilizzi le opzioni predefinite per IntersectionObserver, il callback verrà chiamato sia quando l'elemento diventa parzialmente visibile sia quando lascia completamente l'area visibile.
Se devi osservare più elementi, è possibile e consigliabile farlo utilizzando la stessa istanza IntersectionObserver chiamando observe() più volte.
Al callback viene passato un parametro entries, che è un array di oggetti IntersectionObserverEntry. Ogni oggetto contiene dati di intersezione aggiornati per uno degli elementi osservati.
🔽[IntersectionObserverEntry]
time: 3893.92
🔽rootBounds: ClientRect
bottom: 920
height: 1024
left: 0
right: 1024
top: 0
width: 920
🔽boundingClientRect: ClientRect
// ...
🔽intersectionRect: ClientRect
// ...
intersectionRatio: 0.54
🔽target: div#observee
// ...
rootBounds è il risultato della chiamata di getBoundingClientRect() sull'elemento root, che per impostazione predefinita è l'area visibile. boundingClientRect è il risultato di getBoundingClientRect() chiamato sull'elemento osservato. intersectionRect è l'intersezione di questi due rettangoli e indica quale parte dell'elemento osservato è visibile. intersectionRatio è strettamente correlata e indica quanto dell'elemento è visibile. Con queste informazioni a tua disposizione, ora puoi implementare funzionalità come il caricamento just-in-time degli asset prima che diventino visibili sullo schermo. In modo efficiente.
I IntersectionObserver forniscono i dati in modo asincrono e il codice di callback verrà eseguito nel thread principale. Inoltre, la specifica indica che le implementazioni di IntersectionObserver devono utilizzare requestIdleCallback(). Ciò significa che la chiamata al callback fornito ha una priorità bassa e verrà effettuata dal browser durante il tempo di inattività. Si tratta di una decisione di progettazione consapevole.
Div di scorrimento
Non sono un grande fan dello scorrimento all'interno di un elemento, ma non sono qui per giudicare, e nemmeno IntersectionObserver. L'oggetto options accetta un'opzione root che ti consente di definire un'alternativa alla visualizzazione come root. È importante tenere presente che root deve essere un predecessore di tutti gli elementi osservati.
Intersect all the things!
No! Sviluppatore cattivo! Questo non è un utilizzo consapevole dei cicli della CPU dell'utente. Prendiamo come esempio uno scroller infinito: in questo scenario, è sicuramente consigliabile aggiungere sentinelle al DOM e osservarle (e riciclarle). Devi aggiungere un sentinel vicino all'ultimo elemento dello scroller infinito. Quando l'elemento sentinella diventa visibile, puoi utilizzare il callback per caricare i dati, creare gli elementi successivi, collegarli al DOM e riposizionare l'elemento sentinella di conseguenza. Se ricicli correttamente il sentinel, non è necessaria alcuna chiamata aggiuntiva a observe(). IntersectionObserver continua a funzionare.
Altri aggiornamenti, per favore
Come accennato in precedenza, il callback verrà attivato una volta quando l'elemento osservato diventa parzialmente visibile e un'altra volta quando esce dall'area visibile. In questo modo, IntersectionObserver ti dà una risposta alla domanda "L'elemento X è visibile?". In alcuni casi d'uso, tuttavia, questo potrebbe non essere sufficiente.
È qui che entra in gioco l'opzione threshold. Consente di definire un array di soglie intersectionRatio. Il callback verrà richiamato ogni volta che intersectionRatio supera uno di questi valori. Il valore predefinito per threshold è [0], che spiega il comportamento predefinito. Se cambiamo threshold in [0, 0.25, 0.5, 0.75, 1], riceveremo una notifica ogni volta che diventa visibile un ulteriore quarto dell'elemento:
Esistono altre opzioni?
Al momento, esiste una sola opzione aggiuntiva rispetto a quelle elencate sopra. rootMargin consente di specificare i margini per la radice, permettendoti di aumentare o ridurre l'area utilizzata per le intersezioni. Questi margini vengono specificati utilizzando una stringa di stile CSS, come "10px 20px 30px 40px", che specifica rispettivamente il margine superiore, destro, inferiore e sinistro. In sintesi, la struttura delle opzioni IntersectionObserver offre le seguenti opzioni:
new IntersectionObserver(entries => {/* … */}, {
// The root to use for intersection.
// If not provided, use the top-level document's viewport.
root: null,
// Same as margin, can be 1, 2, 3 or 4 components, possibly negative lengths.
// If an explicit root element is specified, components may be percentages of the
// root element size. If no explicit root element is specified, using a
// percentage is an error.
rootMargin: "0px",
// Threshold(s) at which to trigger callback, specified as a ratio, or list of
// ratios, of (visible area / total area) of the observed element (hence all
// entries must be in the range [0, 1]). Callback will be invoked when the
// visible ratio of the observed element crosses a threshold in the list.
threshold: [0],
});
<iframe> magica
Gli IntersectionObserver sono stati progettati specificamente per i servizi pubblicitari e i widget dei social network, che utilizzano spesso elementi <iframe> e potrebbero trarre vantaggio dal sapere se sono visibili. Se un <iframe> osserva uno dei suoi elementi, lo scorrimento del <iframe> e lo scorrimento della finestra contenente il <iframe> attiveranno il callback nei momenti appropriati. In quest'ultimo caso, tuttavia, rootBounds verrà impostato su null per evitare la fuga di dati tra origini.
Che cosa non è IntersectionObserver?
Tieni presente che IntersectionObserver non è intenzionalmente né pixel perfect né a bassa latenza. Utilizzarli per implementare attività come le animazioni dipendenti dallo scorrimento è destinato a fallire, poiché i dati saranno, a rigor di termini, obsoleti nel momento in cui li utilizzerai. La documentazione esplicativa contiene maggiori dettagli sui casi d'uso originali di IntersectionObserver.
Quanto lavoro posso svolgere nel callback?
In breve: se dedichi troppo tempo al callback, l 'app subirà un ritardo. Si applicano tutte le pratiche comuni.
Vai avanti e interseca i tuoi elementi
Il supporto del browser per IntersectionObserver è buono, poiché è disponibile in tutti i browser moderni. Se necessario, è possibile utilizzare un polyfill nei browser meno recenti, disponibile nel repository di WICG. Ovviamente, l'utilizzo di questo polyfill non ti offrirà i vantaggi in termini di prestazioni che ti darebbe un'implementazione nativa.
Puoi iniziare a utilizzare IntersectionObserver fin da subito. Dicci cosa hai pensato.