Introduzione a visualViewport

Jake Archibald
Jake Archibald

E se ti dicessi che esiste più di un viewport?

BRRRRAAAAAAAMMMMMMMMMM

L'area visibile che stai utilizzando in questo momento è in realtà un'area visibile all'interno di un'altra area visibile.

BRRRRAAAAAAAMMMMMMMMMM

A volte, i dati forniti dal DOM si riferiscono a uno di questi viewport e non all'altro.

BRRRRAAAAM… aspetta, cosa?

È vero, dai un'occhiata:

Area visibile del layout e area visibile visiva

Il video qui sopra mostra una pagina web su cui si esegue lo scorrimento e lo zoom con le dita, insieme a una minimappa sulla destra che mostra la posizione delle visualizzazioni all'interno della pagina.

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

Le cose si complicano quando viene introdotto lo zoom con due dita. La casella rossa rappresenta il viewport visivo, ovvero la parte della pagina che possiamo effettivamente vedere. Questa area visibile può essere spostata, mentre gli elementi position: fixed rimangono dove erano, attaccati all'area visibile del layout. Se eseguiamo la panoramica in corrispondenza di un confine dell'area visibile del layout, questa viene trascinata.

Miglioramento della compatibilità

Purtroppo le API web non sono coerenti in termini di viewport a cui fanno riferimento e non sono coerenti tra i browser.

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

element.getBoundingClientRect().y + window.scrollY

Tuttavia, molti browser utilizzano l'area visibile per window.scrollY, il che significa che il codice riportato sopra si interrompe quando l'utente esegue lo zoom con due dita.

In Chrome 61, window.scrollY fa invece riferimento al viewport del layout, il che significa che il codice riportato sopra funziona anche quando si esegue lo zoom con due dita. Infatti, i browser stanno lentamente modificando tutte le proprietà di posizione in modo che facciano riferimento all'area visibile del layout.

Ad eccezione di una nuova proprietà…

Esposizione dell'area visibile visiva allo script

Una nuova API espone l'area visibile come window.visualViewport. Si tratta di una specifica in bozza, con approvazione tra browser, che verrà implementata in Chrome 61.

console.log(window.visualViewport.width);

Ecco cosa ci fornisce window.visualViewport:

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

Esistono anche un paio di eventi:

window.visualViewport.addEventListener('resize', listener);
visualViewport eventi
resize Viene attivato quando width, height o scale cambia.
scroll Viene attivato quando offsetLeft o offsetTop cambia.

Demo

Il video all'inizio di questo articolo è stato creato utilizzando visualViewport. Guardalo in Chrome 61 e versioni successive. Utilizza visualViewport per fissare la mini-mappa in alto a destra nel visualizzatore e applica una scala inversa in modo che venga sempre visualizzata con le stesse dimensioni, nonostante lo zoom con due dita.

Problemi

Gli eventi vengono attivati solo quando cambia l'area visibile

Sembra una cosa ovvia da affermare, ma mi ha sorpreso quando ho iniziato a giocare con visualViewport.

Se la visualizzazione del layout cambia dimensione, ma non la visualizzazione visiva, non viene generato un eventoresize. Tuttavia, è insolito che l'area visibile del layout venga ridimensionata senza che anche l'area visibile della visualizzazione modifichi larghezza/altezza.

Il vero problema è lo scorrimento. Se si verifica lo scorrimento, ma l'area visibile rimane statica rispetto all'area visibile del layout, non viene generato un evento scroll su visualViewport e questo è molto comune. Durante lo scorrimento normale del documento, l'area visibile rimane bloccata in alto a sinistra dell'area visibile del layout, pertanto scroll non viene attivato su visualViewport.

Se vuoi ascoltare tutte le modifiche al viewport visivo, tra cui 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 il lavoro con più ascoltatori

Analogamente all'ascolto di scroll e resize nella finestra, è probabile che chiamiate qualche tipo di funzione di "aggiornamento" come risultato. Tuttavia, è normale che molti di questi eventi si verifichino contemporaneamente. Se l'utente ridimensiona la finestra, viene attivato resize, ma 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 segnalato un problema relativo alle specifiche, in quanto ritengo che possa esserci un modo migliore, ad esempio un singolo evento update.

I gestori di eventi non funzionano

A causa di un bug di Chrome, non funziona:

Cosa non fare

Bug: 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 (o meglio, spero) che si tratti di un altro bug di Chrome.

offsetLeft e offsetTop sono arrotondati, il che è piuttosto impreciso dopo che l'utente ha aumentato lo zoom. Puoi vedere i problemi relativi a questa opzione durante la demo: se l'utente aumenta lo zoom e esegue la panoramica lentamente, la minimappa scatta tra i pixel non sottoposti a zoom.

La frequenza degli eventi è lenta

Come altri eventi resize e scroll, non vengono attivati a ogni frame, soprattutto sui dispositivi mobili. Puoi vederlo durante la demo: dopo aver aumentato lo zoom con due dita, la minimappa ha difficoltà a rimanere bloccata nell'area visibile.

Accessibilità

Nella demo ho utilizzato visualViewport per compensare lo zoom con due dita dell'utente. Ha senso per questa demo in particolare, ma devi riflettere attentamente prima di fare qualcosa che annulli la volontà dell'utente di eseguire lo zoom.

visualViewport può essere utilizzato per migliorare l'accessibilità. Ad esempio, se l'utente aumenta lo zoom, puoi scegliere di nascondere gli elementi decorativi position: fixed per non intralciarlo. Tuttavia, fai attenzione a non nascondere qualcosa che l'utente sta cercando di esaminare più da vicino.

Potresti prendere in considerazione la pubblicazione su un servizio di analisi quando l'utente aumenta lo zoom. In questo modo, potresti identificare le pagine con cui gli utenti hanno difficoltà al livello di zoom predefinito.

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

È tutto. visualViewport è una piccola API molto utile che risolve i problemi di compatibilità.