¿Y si te dijera que hay más de un viewport?
BRRRAAAAAAAMMMMMMMMMM
Y el viewport que estás usando ahora es en realidad uno dentro de otro.
BRRRAAAAAAAMMMMMMMMMM
Y, a veces, los datos que te proporciona el DOM hacen referencia a uno de esos viewports y no al otro.
BRRRRAAAAM... espera, ¿qué?
Es cierto, mira lo siguiente:
Ventana gráfica de diseño frente a viewport visual
En el video de arriba, se muestra una página web en la que se desplaza y se pellizca, junto con un minimapa a la derecha que muestra la posición de los viewports en la página.
Todo es bastante sencillo durante el desplazamiento normal. El área verde representa el viewport de diseño, al que se adhieren los elementos position: fixed
.
Las cosas se complican cuando se presenta el zoom de pellizcar. El recuadro rojo representa la ventana gráfica visual, que es la parte de la página que realmente se puede ver. Este viewport puede moverse mientras los elementos position: fixed
permanecen donde estaban, adjuntos al viewport de diseño. Si nos desplazamos hacia un límite del viewport de diseño, se arrastrará el viewport de diseño junto con él.
Mejora la compatibilidad
Lamentablemente, las APIs web no son uniformes en cuanto al viewport al que hacen referencia, y tampoco son uniformes en los distintos navegadores.
Por ejemplo, element.getBoundingClientRect().y
muestra el desplazamiento dentro del viewport de diseño. Eso es genial, pero a menudo queremos la posición dentro de la página,
así que escribimos:
element.getBoundingClientRect().y + window.scrollY
Sin embargo, muchos navegadores usan la ventana gráfica visual para window.scrollY
, lo que significa que el código anterior se interrumpe cuando el usuario pellizca la pantalla.
En Chrome 61, se cambia window.scrollY
para hacer referencia al viewport del diseño, lo que significa que el código anterior funciona incluso cuando se pellizca el zoom. De hecho, los navegadores están cambiando lentamente todas las propiedades posicionales para hacer referencia al viewport del diseño.
Con la excepción de una propiedad nueva...
Cómo exponer la viewport visual a una secuencia de comandos
Una nueva API expone el viewport visual como window.visualViewport
. Es una especificación en borrador, con aprobación en varios navegadores, y llega a Chrome 61.
console.log(window.visualViewport.width);
window.visualViewport
nos brinda lo siguiente:
visualViewport propiedades |
|
---|---|
offsetLeft
|
Distancia entre el borde izquierdo del viewport visual y el viewport de diseño, en píxeles de CSS. |
offsetTop
|
Distancia entre el borde superior del viewport visual y el viewport de diseño, en píxeles de CSS. |
pageLeft
|
Distancia entre el borde izquierdo del viewport visual y el límite izquierdo del documento, en píxeles de CSS. |
pageTop
|
Distancia entre el borde superior del viewport visual y el límite superior del documento, en píxeles de CSS. |
width
|
Ancho de la viewport visual en píxeles de CSS. |
height
|
Es la altura de la viewport visual en píxeles de CSS. |
scale
|
Es la escala que se aplica al pellizcar la pantalla. Si el contenido tiene el doble de tamaño debido al zoom, se mostrará 2 . No se ve afectado por
devicePixelRatio .
|
También hay un par de eventos:
window.visualViewport.addEventListener('resize', listener);
visualViewport de eventos |
|
---|---|
resize
|
Se activa cuando cambian width , height o scale .
|
scroll
|
Se activa cuando cambian offsetLeft o offsetTop .
|
Demostración
El video al comienzo de este artículo se creó con visualViewport
(puedes verlo en Chrome 61 y versiones posteriores). Utiliza visualViewport
para hacer que el minimapa se adhiera a la parte superior derecha de la ventana gráfica visual y aplica una escala inversa para que siempre se vea del mismo tamaño, a pesar del zoom.
Problemas
Los eventos solo se activan cuando cambia el viewport visual
Parece algo obvio, pero me llamó la atención cuando jugué con visualViewport
por primera vez.
Si el viewport del diseño cambia de tamaño, pero el viewport visual no, no obtendrás un evento resize
. Sin embargo, no es común que el viewport del diseño cambie de tamaño sin que el viewport visual cambie también el ancho o la altura.
La verdadera trampa es desplazarse. Si se produce el desplazamiento, pero el viewport visual permanece estático en relación con el viewport de diseño, no se obtiene un evento scroll
en visualViewport
, y esto es muy común. Durante el desplazamiento normal del documento, el viewport visual permanece bloqueado en la parte superior izquierda del viewport de diseño, por lo que scroll
no se activa en visualViewport
.
Si deseas escuchar todos los cambios en el viewport visual, incluidos pageTop
y pageLeft
, también tendrás que escuchar el evento de desplazamiento de la ventana:
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
Evita duplicar el trabajo con varios objetos de escucha
De manera similar a cuando escuchas scroll
y resize
en la ventana, es probable que llames a algún tipo de función de "actualización". Sin embargo, es común que muchos de estos eventos ocurran al mismo tiempo. Si el usuario cambia el tamaño de la ventana, se activará resize
, pero a menudo también scroll
. Para mejorar el rendimiento, evita manejar el cambio varias veces:
// 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
});
}
Envié un problema de especificaciones sobre esto, ya que creo que puede haber una mejor manera, como un solo evento update
.
Los controladores de eventos no funcionan
Debido a un error de Chrome, esto no funciona:
Buggy: usa un controlador de eventos.
visualViewport.onscroll = () => console.log('scroll!');
En su lugar, siga estos pasos:
Funciona: usa un objeto de escucha de eventos.
visualViewport.addEventListener('scroll', () => console.log('scroll'));
Los valores de desplazamiento se redondean.
Creo (bueno, espero) que es otro error de Chrome.
offsetLeft
y offsetTop
están redondeados, lo cual es bastante inexacto una vez que el usuario acerca la imagen. Puedes ver los problemas relacionados con este proceso durante la demostración. Si el usuario acerca la imagen y se desplaza lentamente, el minimapa se ajusta entre los píxeles sin zoom.
La frecuencia de eventos es lenta
Al igual que otros eventos resize
y scroll
, no se activan en todos los fotogramas, especialmente en dispositivos móviles. Puedes verlo durante la demostración: una vez que pellizcas el zoom, el minimapa tiene dificultades para permanecer bloqueado en el viewport.
Accesibilidad
En la demostración, usé visualViewport
para contrarrestar el zoom de pellizcar del usuario. Tiene sentido para esta demostración en particular, pero debes pensar con cuidado antes de hacer algo que anule el deseo del usuario de acercar la imagen.
Se puede usar visualViewport
para mejorar la accesibilidad. Por ejemplo, si el usuario está acercando la imagen, puedes optar por ocultar los elementos decorativos de position: fixed
para que no se distraigan. Pero, de nuevo, ten cuidado de no ocultar algo que el usuario intenta examinar con más detalle.
Podrías considerar publicar en un servicio de estadísticas cuando el usuario acerque el mapa. Esto podría ayudarte a identificar las páginas con las que los usuarios tienen dificultades con el nivel de zoom predeterminado.
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
Eso es todo. visualViewport
es una API pequeña y útil que resuelve los problemas de compatibilidad en el proceso.