¿Qué sucede si te digo que hay más de un viewport?
BRRRRAAAAAAAMMMMMMMMMM
Y el viewport que estás usando en este momento es, en realidad, un viewport dentro de un panorama.
BRRRRAAAAAAAMMMMMMMMMM
Y, a veces, los datos que te proporciona el DOM se refieren a uno de esos viewports y no al otro.
BRRRRAAAAM… ¿Qué?
Es verdad, mira:
Viewport de diseño frente a viewport visual
En el video anterior, se muestra una página web en la que se desplaza el contenido y se aplica el gesto de pellizcar para acercar, junto con un minimapa a la derecha que muestra la posición de los viewports dentro de la página.
Todo es bastante sencillo durante el desplazamiento normal. El área verde representa la vista del puerto de diseño, a la que se adhieren los elementos position: fixed
.
Las cosas se ponen raras cuando se introduce el zoom con pellizco. El cuadro rojo representa el viewport visual, que es la parte de la página que realmente podemos ver. Esta ventana de visualización puede moverse mientras los elementos position: fixed
permanecen donde estaban, unidos a la ventana de visualización del diseño. Si desplazamos el contenido en un límite del viewport del diseño, se arrastra el viewport del diseño junto con él.
Mejora la compatibilidad
Lamentablemente, las APIs web no son coherentes en cuanto al viewport al que hacen referencia ni en los navegadores.
Por ejemplo, element.getBoundingClientRect().y
muestra el desplazamiento dentro del viewport de diseño. Eso está bien, pero a menudo queremos la posición dentro de la página, así que escribimos lo siguiente:
element.getBoundingClientRect().y + window.scrollY
Sin embargo, muchos navegadores usan el viewport visual para window.scrollY
, lo que significa que el código anterior se interrumpe cuando el usuario realiza un gesto de pellizco para acercar.
Chrome 61 cambia window.scrollY
para hacer referencia al viewport del diseño, lo que significa que el código anterior funciona incluso cuando se acerca con el gesto de pellizcar. De hecho, los navegadores cambian lentamente todas las propiedades de posición para hacer referencia al viewport del diseño.
Con la excepción de una propiedad nueva…
Cómo exponer la vista visual a la secuencia de comandos
Una nueva API expone el viewport visual como window.visualViewport
. Es un borrador de especificaciones, con aprobación multinavegador y se lanzará en Chrome 61.
console.log(window.visualViewport.width);
Esto es lo que nos brinda window.visualViewport
:
visualViewport propiedades |
|
---|---|
offsetLeft
|
Es la distancia entre el borde izquierdo del viewport visual y el viewport de diseño, en píxeles de CSS. |
offsetTop
|
Es la distancia entre el borde superior del viewport visual y el viewport de diseño, en píxeles CSS. |
pageLeft
|
Es la distancia entre el borde izquierdo del viewport visual y el límite izquierdo del documento, en píxeles CSS. |
pageTop
|
Es la distancia entre el borde superior de la ventana de visualización visual y el límite superior del documento, en píxeles CSS. |
width
|
Es el ancho del viewport visual en píxeles de CSS. |
height
|
Es la altura del viewport visual en píxeles CSS. |
scale
|
Es la escala que se aplica con el zoom de pellizco. Si el contenido tiene el doble de tamaño debido al zoom, se mostrará 2 . Esto no se ve afectado por devicePixelRatio .
|
También hay algunos eventos:
window.visualViewport.addEventListener('resize', listener);
visualViewport eventos |
|
---|---|
resize
|
Se activa cuando cambia width , height o scale .
|
scroll
|
Se activa cuando cambia offsetLeft o offsetTop .
|
Demostración
El video que aparece al comienzo de este artículo se creó con visualViewport
. Míralo en Chrome 61 y versiones posteriores. Usa visualViewport
para que el mapa en miniatura se adhiera a la parte superior derecha del viewport visual y aplica una escala inversa para que siempre aparezca del mismo tamaño, a pesar de pellizcar para hacer zoom.
Problemas
Los eventos solo se activan cuando cambia el viewport visual.
Parece algo obvio, pero me sorprendió cuando probé visualViewport
por primera vez.
Si el tamaño de la vista de diseño cambia, pero no el de la vista visual, no se recibe un evento resize
. Sin embargo, es inusual que el viewport del diseño cambie de tamaño sin que el viewport visual también cambie de ancho o alto.
El verdadero problema es el desplazamiento. 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, la ventana de visualización visual permanece bloqueada en la parte superior izquierda de la ventana de visualización del diseño, por lo que scroll
no se activa en visualViewport
.
Si quieres escuchar todos los cambios en el viewport visual, incluidos pageTop
y pageLeft
, también deberás 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
Al igual que escuchar scroll
y resize
en la ventana, es probable que llames a algún tipo de función "update" como resultado. 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 también scroll
con bastante frecuencia. Para mejorar el rendimiento, evita controlar 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
});
}
Enviamos un problema de especificación para esto, ya que creemos que puede haber una mejor manera, como un solo evento update
.
Los controladores de eventos no funcionan
Debido a un error de Chrome, no funciona:
Tiene errores: 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 este sea otro error de Chrome.
offsetLeft
y offsetTop
se redondean, lo que es bastante inexacto una vez que el usuario acercó la imagen. Puedes ver los problemas con esto durante la demostración. Si el usuario acerca la imagen y se desplaza lentamente, el minimapa se ajusta entre píxeles sin acercar.
La tasa de eventos es lenta
Al igual que otros eventos resize
y scroll
, no se activan en cada fotograma, especialmente en dispositivos móviles. Puedes ver esto durante la demostración: una vez que pellizcas para acercar, el mapa en miniatura
tiene problemas para permanecer bloqueado en el viewport.
Accesibilidad
En la demostración, usé visualViewport
para contrarrestar el zoom con pellizco del usuario. Tiene sentido para esta demostración en particular, pero debes pensar cuidadosamente 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 acerca la imagen, puedes ocultar los elementos decorativos position: fixed
para que no le estorben. Pero, una vez más, ten cuidado de no ocultar algo que el usuario intenta ver con más detalle.
Puedes considerar publicar en un servicio de estadísticas cuando el usuario acerque la imagen. Esto podría ayudarte a identificar las páginas con las que los usuarios tienen dificultades en 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 agradable que resuelve los problemas de compatibilidad en el camino.