Que se passerait-il si je vous disais qu'il existe plusieurs vues ?
BRRRRAAAAAAAMMMMMMMMMM
La fenêtre d'affichage que vous utilisez actuellement est en fait une fenêtre d'affichage dans une fenêtre d'affichage.
BRRRRAAAAAAAMMMMMMMMMM
Parfois, les données que le DOM vous fournit font référence à l'un de ces viewports et non à l'autre.
BRRRRAAAAM… Qu'est-ce que vous dites ?
C'est vrai. Regardez:
Fenêtre d'affichage de la mise en page par rapport à la fenêtre d'affichage visuelle
La vidéo ci-dessus montre le défilement et le zoom avec pincement d'une page Web, ainsi qu'une mini-carte à droite montrant la position des vues dans la page.
Le défilement standard est assez simple. La zone verte représente la fenêtre d'affichage de la mise en page, à laquelle les éléments position: fixed
s'accrochent.
Les choses deviennent bizarres lorsque le zoom par pincement est introduit. Le cadre rouge représente la fenêtre d'affichage visuelle, 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 à leur place, attachés à la fenêtre d'affichage de la mise en page. Si nous effectuons un panoramique à la limite de la fenêtre d'affichage de la mise en page, la fenêtre d'affichage de la mise en page est entraînée.
Améliorer la compatibilité
Malheureusement, les API Web ne sont pas cohérentes en termes de vue à laquelle elles font référence, et elles ne le sont pas non plus 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 bien, mais nous souhaitons souvent connaître la position sur la page. Nous écrivons donc:
element.getBoundingClientRect().y + window.scrollY
Toutefois, de nombreux navigateurs utilisent la fenêtre d'affichage visuelle pour window.scrollY
, ce qui signifie que le code ci-dessus ne fonctionne pas lorsque l'utilisateur fait un pincement pour zoomer.
Chrome 61 modifie window.scrollY
pour qu'il fasse référence au viewport de mise en page. Cela signifie que le code ci-dessus fonctionne même en cas de zoom avec pincement. En fait, les navigateurs modifient progressivement toutes les propriétés de position pour faire référence à la fenêtre d'affichage de la mise en page.
À l'exception d'une nouvelle propriété…
Exposer 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'une spécification provisoire, avec une approbation multinavigateur, et elle sera disponible 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 visuelle 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 gauche du document, en pixels CSS. |
pageTop
|
Distance entre le bord supérieur de la fenêtre d'affichage visuelle 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 visuelle en pixels CSS. |
scale
|
Échelle appliquée en effectuant un pincement pour zoomer. Si le contenu est deux fois plus grand en raison du zoom, la valeur 2 est renvoyée. Cela n'est pas affecté par devicePixelRatio .
|
Il existe également quelques événements:
window.visualViewport.addEventListener('resize', listener);
visualViewport événements |
|
---|---|
resize
|
Se déclenche lorsque width , height ou scale change.
|
scroll
|
Déclenché lorsque offsetLeft ou offsetTop change.
|
Démo
La vidéo au début de cet article a été créée à l'aide de visualViewport
. Découvrez-la dans Chrome 61 ou version ultérieure. Elle utilise visualViewport
pour que le mini-plan se fixe en haut à droite du viewport visuel et applique une échelle inverse pour qu'il apparaisse toujours de la même taille, malgré le zoom avec pincement.
Pièges
Les événements ne se déclenchent que lorsque la fenêtre d'affichage visuelle change.
Cela semble évident, mais cela m'a surpris lorsque j'ai commencé à jouer avec visualViewport
.
Si la vue du canevas est redimensionnée, mais pas la vue visuelle, vous ne recevez pas d'événement resize
. Toutefois, il est inhabituel que la fenêtre d'affichage de mise en page soit redimensionnée sans que la largeur/hauteur de la fenêtre d'affichage visuelle ne change également.
Le véritable problème est le défilement. Si le défilement se produit, mais que la fenêtre d'affichage visuelle reste statique par rapport à la fenêtre d'affichage de la mise en page, vous ne recevez pas d'événement scroll
sur visualViewport
, ce qui est très courant. Lors du défilement normal du document, la fenêtre d'affichage visuelle reste verrouillée en haut à gauche de la fenêtre d'affichage de la mise en page. scroll
ne se déclenche donc pas sur visualViewport
.
Si vous souhaitez connaître toutes les modifications apportées au viewport visuel, 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);
Éviter de dupliquer le travail avec plusieurs écouteurs
Comme pour l'écoute de scroll
et resize
sur la fenêtre, vous êtes susceptible d'appeler une sorte de fonction "update" en conséquence. Cependant, il est courant que plusieurs de ces événements se produisent en même temps. Si l'utilisateur redimensionne la fenêtre, resize
est déclenché, mais aussi très 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 de spécification à ce sujet, car je pense qu'il existe peut-être une meilleure méthode, comme un seul événement update
.
Les gestionnaires d'événements ne fonctionnent pas
En raison d'un bug de Chrome, les opérations suivantes ne fonctionnent pas:
Buggé : utilise un gestionnaire d'événements
visualViewport.onscroll = () => console.log('scroll!');
Au lieu de cela :
Fonctionne : utilise un écouteur d'événements
visualViewport.addEventListener('scroll', () => console.log('scroll'));
Les valeurs de décalage sont arrondies
Je pense (enfin, j'espère) qu'il s'agit d'un autre bug 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 liés à cette approche pendant la démonstration. Si l'utilisateur fait un zoom avant et un panoramique lent, la mini-carte se fixe entre les pixels non agrandis.
Le taux d'événements est lent
Comme les autres événements resize
et scroll
, ils ne se déclenchent pas à chaque frame, en particulier sur mobile. Vous pouvez le constater lors de la démo : une fois que vous effectuez un pincement pour faire un zoom avant, la mini-carte a du mal à rester verrouillée sur la fenêtre d'affichage.
Accessibilité
Dans la démo, j'ai utilisé visualViewport
pour contrecarrer le zoom avec pincement de l'utilisateur. Cela a du sens pour cette démonstration particulière, mais vous devez réfléchir attentivement avant de faire quoi que ce soit qui remplace le désir de l'utilisateur de faire un zoom avant.
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 position: fixed
décoratifs pour les éloigner de l'utilisateur. Encore une fois, veillez à ne pas masquer quelque chose que l'utilisateur essaie de voir de plus près.
Vous pouvez envisager de publier des données dans un service d'analyse lorsque l'utilisateur fait un zoom avant. Cela peut vous aider à identifier les pages avec lesquelles les utilisateurs ont des difficultés au niveau du zoom par défaut.
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
Et voilà ! visualViewport
est une petite API pratique qui résout les problèmes de compatibilité au fil du temps.