Introduzione a visualViewport

Jake Archibald
Jake Archibald

Se te lo dico, esiste più di un'area visibile.

BRRRRAAAAAAAMMMMMMMM

L'area visibile in uso in questo momento è in realtà un'area visibile all'interno di un'area visibile.

BRRRRAAAAAAAMMMMMMMM

A volte, i dati forniti dal DOM si riferiscono a una di queste aree visibili e non all'altra.

BRRRRAAAAM... cosa?

È vero, dai un'occhiata:

Area visibile del layout e area visibile visiva

Il video in alto mostra una pagina web che viene eseguita lo scorrimento e pizzica lo zoom, insieme a una mini-mappa sulla destra che mostra la posizione delle aree visibili all'interno della pagina.

Durante lo scorrimento regolare le cose sono piuttosto semplici. L'area verde rappresenta l'area visibile del layout, a cui si fissano position: fixed elementi.

Le cose si fanno strane quando si adotta lo zoom tramite pizzico. La casella rossa rappresenta l'area visibile visiva, ovvero la parte della pagina che possiamo effettivamente vedere. Questo viewport può essere spostato mentre gli elementi position: fixed rimangono nella posizione in cui si trovavano, collegati all'area visibile del layout. Se eseguiamo la panoramica in corrispondenza di un confine dell'area visibile del layout, viene trascinata anche l'area visibile del layout.

Miglioramento della compatibilità

Sfortunatamente, le API web sono incoerenti per quanto riguarda l'area visibile a cui fanno riferimento e da un browser all'altro.

Ad esempio, element.getBoundingClientRect().y restituisce l'offset all'interno dell'area visibile del layout. Fantastico, ma spesso vogliamo la posizione all'interno della pagina, quindi scriviamo:

element.getBoundingClientRect().y + window.scrollY

Tuttavia, molti browser utilizzano l'area visibile visiva per window.scrollY, il che significa che il codice riportato sopra non funziona quando l'utente esegue lo zoom con le dita.

Chrome 61 modifica window.scrollY per fare riferimento all'area visibile del layout, il che significa che il codice riportato sopra funziona anche quando viene eseguito lo zoom tramite pizzico. Infatti, i browser stanno modificando lentamente tutte le proprietà di posizionamento per fare riferimento all'area visibile del layout.

Ad eccezione di una nuova proprietà...

Esposizione dell'area visibile allo script

Una nuova API espone l'area visibile visiva come window.visualViewport. Si tratta di una bozza di specifica, con approvazione cross-browser, che arriva in Chrome 61.

console.log(window.visualViewport.width);

Ecco cosa ci offre window.visualViewport:

visualViewport strutture
offsetLeft Distanza tra il bordo sinistro dell'area visibile visiva e l'area visibile del layout, in pixel CSS.
offsetTop Distanza tra il bordo superiore dell'area visibile visiva e l'area visibile del layout, in pixel CSS.
pageLeft Distanza tra il bordo sinistro dell'area visibile visiva e il limite sinistro del documento, in pixel CSS.
pageTop Distanza tra il bordo superiore dell'area visibile visiva e il limite superiore del documento, in pixel CSS.
width Larghezza dell'area visibile in pixel CSS.
height Altezza dell'area visibile visiva in pixel CSS.
scale La scala applicata da pizzicare con lo zoom. Se i contenuti hanno una dimensione doppia a causa dello zoom, viene restituito 2. Questo non è interessato da devicePixelRatio.

Ci sono anche un paio di eventi:

window.visualViewport.addEventListener('resize', listener);
visualViewport di eventi
resize Attivato quando vengono modificati width, height o scale.
scroll Attivato quando cambiano le impostazioni offsetLeft o offsetTop.

Demo

Il video all'inizio di questo articolo è stato creato utilizzando visualViewport, dai un'occhiata in Chrome 61 e versioni successive. Utilizza visualViewport per fissare la mini mappa nella parte in alto a destra dell'area visibile e applica una scala inversa in modo che appaia sempre delle stesse dimensioni, nonostante lo zoom sia pizzicato.

Oggetti utili

Gli eventi vengono attivati solo quando cambia l'area visibile visiva

Sembra ovvio da dire, ma mi ha colpito quando ho giocato per la prima volta con visualViewport.

Se l'area visibile del layout si ridimensiona, ma non quella visiva, non ricevi un evento resize. Tuttavia, è insolito che l'area visibile del layout venga ridimensionata senza che anche l'area visibile visiva cambi larghezza/altezza.

Il segreto è scorrere. Se viene eseguito lo scorrimento, ma l'area visibile rimane statica rispetto all'area visibile del layout, non ricevi un evento scroll su visualViewport, cosa molto comune. Durante lo scorrimento normale del documento, l'area visibile rimane bloccata in alto a sinistra nell'area visibile del layout, quindi scroll non si attiva su visualViewport.

Se vuoi conoscere tutte le modifiche all'area visibile, incluse pageTop e pageLeft, dovrai ascoltare anche l'evento di scorrimento della finestra:

visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);

Evitare di duplicare i lavori con più ascoltatori

Come per l'ascolto di scroll e resize sulla finestra, probabilmente richiamerai una funzione di aggiornamento. Tuttavia, è comune che molti di questi eventi si verifichino contemporaneamente. Se l'utente ridimensiona la finestra, si attiverà resize, ma molto spesso anche scroll. Per migliorare le prestazioni, evita di gestire la modifica più volte:

// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);

let pendingUpdate = false;

function update() {
    // If we're already going to handle an update, return
    if (pendingUpdate) return;

    pendingUpdate = true;

    // Use requestAnimationFrame so the update happens before next render
    requestAnimationFrame(() => {
    pendingUpdate = false;

    // Handle update here
    });
}

Ho presentato un problema relativo alle specifiche, perché ritengo che ci sia un modo migliore, ad esempio un singolo evento update.

I gestori di eventi non funzionano

A causa di un bug di Chrome, la procedura non funziona:

Cosa non fare

Buggy: utilizza un gestore di eventi.

visualViewport.onscroll = () => console.log('scroll!');

Invece:

Cosa fare

Funziona: utilizza un listener di eventi

visualViewport.addEventListener('scroll', () => console.log('scroll'));

I valori di offset sono arrotondati

Penso (beh, spero) che questo sia un altro bug di Chrome.

offsetLeft e offsetTop sono arrotondati, il che non è preciso una volta che l'utente ha aumentato lo zoom. Puoi verificarli durante la demo: se l'utente aumenta lo zoom e fa una panoramica lentamente, la mini-mappa si aggancia tra i pixel non ingranditi.

La frequenza di eventi è lenta

Come altri eventi resize e scroll, questi eventi non vengono attivati a ogni frame, soprattutto sui dispositivi mobili. Puoi verificarlo durante la demo: dopo aver pizzicato lo zoom, la mini-mappa ha problemi a rimanere bloccata sull'area visibile.

Accessibilità

Nella demo ho utilizzato visualViewport per contrapporre lo zoom tramite pizzico dell'utente. Può essere logico per questa particolare demo, ma devi riflettere attentamente prima di fare qualsiasi cosa che sostituisca il desiderio dell'utente di aumentare lo zoom.

L'app visualViewport può essere utilizzata per migliorare l'accessibilità. Ad esempio, se l'utente aumenta lo zoom, puoi scegliere di nascondere elementi position: fixed decorativi per allontanarli dall'utente. Ma, ancora una volta, fai attenzione a non nascondere qualcosa che l'utente sta cercando di vedere più da vicino.

Potresti valutare la possibilità di pubblicare un post su un servizio di analisi quando l'utente aumenta lo zoom. Questo potrebbe aiutarti a identificare le pagine con cui gli utenti hanno difficoltà con il livello di zoom predefinito.

visualViewport.addEventListener('resize', () => {
    if (visualViewport.scale > 1) {
    // Post data to analytics service
    }
});

e il gioco è fatto. visualViewport è una piccola API che risolve i problemi di compatibilità.