Présentation de visualViewport

Jake Archibld
Jake Archibald

Supposons qu'il y ait plusieurs fenêtres d'affichage.

BRRRRAAAAAAAMMMMMMMMMM

La fenêtre d'affichage que vous utilisez actuellement est en réalité une fenêtre dans une fenêtre d'affichage.

BRRRRAAAAAAAMMMMMMMMMM

Parfois, les données fournies par le DOM font référence à l'une de ces fenêtres d'affichage et non à l'autre.

BRRRRAAAAM... attendez quoi ?

C'est vrai. Regardez:

Fenêtre d'affichage de la mise en page ou fenêtre d'affichage visuelle

La vidéo ci-dessus montre une page Web que l'on fait défiler et que l'on pince pour zoomer, ainsi qu'à droite une mini-carte indiquant la position des fenêtres d'affichage sur la page.

Tout est assez simple lors du défilement régulier. La zone verte représente la fenêtre d'affichage de la mise en page, à laquelle les éléments position: fixed restent.

Les choses deviennent bizarres lorsque le zoom par pincement est introduit. Le cadre rouge représente la fenêtre d'affichage, qui correspond à la partie de la page que nous pouvons réellement voir. Cette fenêtre d'affichage peut se déplacer tandis que les éléments position: fixed restent là où ils se trouvaient, associés à la fenêtre d'affichage de mise en page. Si nous effectuons un panoramique à une limite de la fenêtre d'affichage de mise en page, la fenêtre d'affichage de mise en page est déplacée avec elle.

Améliorer la compatibilité

Malheureusement, les API Web ne sont pas les mêmes en termes de fenêtre d'affichage, mais aussi d'un navigateur à l'autre.

Par exemple, element.getBoundingClientRect().y renvoie le décalage dans la fenêtre d'affichage de la mise en page. C'est cool, mais nous voulons souvent la position dans la page. C'est pourquoi nous écrivons:

element.getBoundingClientRect().y + window.scrollY

Cependant, de nombreux navigateurs utilisent la fenêtre d'affichage pour window.scrollY, ce qui signifie que le code ci-dessus ne fonctionne pas lorsque l'utilisateur pince pour zoomer.

Chrome 61 modifie window.scrollY pour qu'il fasse référence à la fenêtre d'affichage de mise en page à la place, ce qui signifie que le code ci-dessus fonctionne même lorsque l'utilisateur effectue un zoom par pincement. En fait, les navigateurs modifient lentement toutes les propriétés de position pour faire référence à la fenêtre d'affichage de mise en page.

À l'exception d'une nouvelle propriété...

Présenter la fenêtre d'affichage visuelle au script

Une nouvelle API expose la fenêtre d'affichage visuelle en tant que window.visualViewport. Il s'agit d'un brouillon de spécification, avec approbation internavigateur, qui arrive dans Chrome 61.

console.log(window.visualViewport.width);

Voici ce que window.visualViewport nous donne:

visualViewport établissements
offsetLeft Distance entre le bord gauche de la fenêtre d'affichage visuelle et la fenêtre d'affichage de la mise en page, en pixels CSS.
offsetTop Distance entre le bord supérieur de la fenêtre d'affichage et la fenêtre d'affichage de la mise en page, en pixels CSS.
pageLeft Distance entre le bord gauche de la fenêtre d'affichage visuelle et la limite de gauche du document, en pixels CSS.
pageTop Distance entre le bord supérieur de la fenêtre d'affichage et la limite supérieure du document, en pixels CSS.
width Largeur de la fenêtre d'affichage visuelle en pixels CSS.
height Hauteur de la fenêtre d'affichage en pixels CSS.
scale Échelle appliquée en effectuant un zoom par pincement. Si le contenu fait deux fois la taille du contenu en raison du zoom, la valeur renvoyée est 2. Cela n'est pas affecté par devicePixelRatio.

Il existe également quelques événements:

window.visualViewport.addEventListener('resize', listener);
visualViewport événements
resize Déclenché lorsque width, height ou scale change.
scroll Déclenché lorsque offsetLeft ou offsetTop change.

Démonstration

La vidéo au début de cet article a été créée avec visualViewport. Consultez-la dans Chrome 61 ou une version ultérieure. Elle utilise visualViewport pour que la mini-carte reste en haut à droite de la fenêtre d'affichage et applique une échelle inverse afin qu'elle s'affiche toujours à la même taille, malgré le pincement de zoom.

Problèmes

Les événements ne se déclenchent que lorsque la fenêtre d'affichage visuelle change

Cela semble évident, mais cela m'a surpris(e) quand j'ai joué avec visualViewport pour la première fois.

Si la fenêtre d'affichage de mise en page est redimensionnée, mais pas la fenêtre d'affichage visuelle, vous ne recevez pas d'événement resize. Cependant, il est inhabituel que la fenêtre d'affichage de mise en page soit redimensionnée sans que la fenêtre d'affichage visuelle ne change également de largeur/hauteur.

Le vrai piège, c'est le défilement. En cas de défilement, mais que la fenêtre d'affichage visuelle reste statique par rapport à la fenêtre d'affichage de mise en page, vous ne recevez pas d'événement scroll sur visualViewport, ce qui est très courant. Lors du défilement normal des documents, la fenêtre d'affichage visuelle reste verrouillée en haut à gauche de la fenêtre d'affichage de la mise en page. Par conséquent, scroll ne se déclenche pas sur visualViewport.

Si vous souhaitez être informé de toutes les modifications apportées à la fenêtre d'affichage visuelle, y compris pageTop et pageLeft, vous devez également écouter l'événement de défilement de la fenêtre:

visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);

Évitez de dupliquer des tâches avec plusieurs écouteurs

Comme pour l'écoute de scroll et resize sur la fenêtre, vous serez probablement amené à appeler une sorte de fonction "update". Cependant, il est courant qu'un grand nombre de ces événements se produisent en même temps. Si l'utilisateur redimensionne la fenêtre, resize se déclenchera, mais aussi souvent scroll. Pour améliorer les performances, évitez de gérer la modification plusieurs fois:

// 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
    });
}

J'ai signalé un problème spécifique, car je pense qu'il existe une meilleure solution, par exemple un seul événement update.

Les gestionnaires d'événements ne fonctionnent pas

En raison d'un bug dans Chrome, cette opération ne fonctionne pas:

À éviter

Buggy (utilise un gestionnaire d'événements)

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

Au lieu de cela :

À faire

Fonctionne (utilise un écouteur d'événements)

visualViewport.addEventListener('scroll', () => console.log('scroll'));

Les valeurs de décalage sont arrondies

Je crois qu'il s'agit d'un autre bug de Chrome.

offsetLeft et offsetTop sont arrondis, ce qui est assez inexact une fois que l'utilisateur a fait un zoom avant. Vous pouvez voir les problèmes que cela pose problème lors de la démonstration. Si l'utilisateur fait un zoom avant et fait un panoramique lentement, la mini-carte s'ancre entre les pixels non zoomés.

Le taux d'événements est lent

Comme les autres événements resize et scroll, ils ne se déclenchent pas tous les frames, en particulier sur mobile. Vous pouvez le constater lors de la démonstration. Lorsque vous pincez le zoom, la mini-carte ne parvient pas à rester verrouillée dans la fenêtre d'affichage.

Accessibilité

Dans la démonstration, j'ai utilisé visualViewport pour neutraliser le zoom par pincement de l'utilisateur. Cette démonstration est tout à fait logique, mais vous devez bien réfléchir avant d'effectuer une action qui ignore le désir de l'utilisateur de zoomer.

visualViewport peut être utilisé pour améliorer l'accessibilité. Par exemple, si l'utilisateur fait un zoom avant, vous pouvez choisir de masquer les éléments décoratifs position: fixed pour les empêcher de le faire. Mais là encore, veillez à ne pas cacher quelque chose que l'utilisateur essaie d'examiner de plus près.

Vous pouvez envisager de publier sur un service d'analyse lorsque l'utilisateur fait un zoom avant. Cela peut vous aider à identifier les pages avec lesquelles les utilisateurs rencontrent des difficultés au niveau de zoom par défaut.

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

Voilà, c'est terminé ! visualViewport est une petite API qui résout les problèmes de compatibilité en cours de route.