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:
Bug: utilizza un gestore di eventi
visualViewport.onscroll = () => console.log('scroll!');
Invece:
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à.