Presentación de visualViewport

¿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:

Qué no debes hacer

Tiene errores: usa un controlador de eventos.

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

En su lugar, siga estos pasos:

Qué debes hacer

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.