10 octobre 2020

Taille des éléments et défilement

Il existe de nombreuses propriétés JavaScript qui nous permettent de lire des informations sur la largeur, la hauteur des éléments et d’autres caractéristiques géométriques.

Nous en avons souvent besoin lors du déplacement ou du positionnement d’éléments en JavaScript.

Exemple d’élément

Comme exemple d’élément pour démontrer les propriétés, nous utiliserons celui donné ci-dessous :

<div id="example">
  ...Text...
</div>
<style>
  #example {
    width: 300px;
    height: 200px;
    border: 25px solid #E8C48F;
    padding: 20px;
    overflow: auto;
  }
</style>

Il a la bordure, le padding et le défilement. L’ensemble complet de fonctionnalités. Il n’y a pas de marges, car elles ne font pas partie de l’élément lui-même et il n’y a pas de propriétés spéciales pour elles.

L’élément ressemble à ceci :

Vous pouvez ouvrir le document dans la sandbox.

Attention à la barre de défilement

L’image ci-dessus illustre le cas le plus complexe lorsque l’élément a une barre de défilement. Certains navigateurs (pas tous) lui réservent de l’espace en le prenant dans le contenu (étiqueté comme “largeur de contenu” ci-dessus).

Ainsi, sans barre de défilement, la largeur du contenu serait de 300px, mais si la barre de défilement est large de 16 pixels (la largeur peut varier entre les appareils et les navigateurs), il ne reste que 300 - 16 = 284 pixels, et nous devons en tenir compte. . C’est pourquoi les exemples de ce chapitre supposent qu’il y a une barre de défilement. Sans cela, certains calculs sont plus simples.

La zone padding-bottom peut être remplie de texte

Habituellement, les paddings sont vides sur nos illustrations, mais s’il y a beaucoup de texte dans l’élément et qu’il déborde, alors les navigateurs affichent le texte “débordant” dans padding-bottom, c’est normal.

Géométrie

Voici l’image globale avec les propriétés de la géométrie :

Les valeurs de ces propriétés sont techniquement des nombres, mais ces nombres sont “de pixels”, donc ce sont des mesures de pixels.

Commençons à explorer les propriétés à partir de l’extérieur de l’élément.

offsetParent, offsetLeft/Top

Ces propriétés sont rarement nécessaires, mais ce sont toujours les propriétés de géométrie “les plus extérieures”, nous allons donc commencer par elles.

Le offsetParent est l’ancêtre le plus proche que le navigateur utilise pour calculer les coordonnées pendant le rendu.

C’est l’ancêtre le plus proche qui est l’un des suivants :

  1. CSS positionné (position is absolute, relative, fixed or sticky), ou
  2. <td>, <th>, ou <table>, ou
  3. <body>.

Les propriétés offsetLeft/offsetTop fournissent des coordonnées x/y par rapport au coin supérieur gauche de offsetParent.

Dans l’exemple ci-dessous, la <div> intérieure a <main> comme offsetParent et offsetLeft/offsetTop décale de son coin supérieur gauche (180) :

<main style="position: relative" id="main">
  <article>
    <div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
  </article>
</main>
<script>
  alert(example.offsetParent.id); // main
  alert(example.offsetLeft); // 180 (note : un nombre, pas une chaîne de caractères "180px")
  alert(example.offsetTop); // 180
</script>

Il y a plusieurs occasions où offsetParent est null :

  1. Pour les éléments non affichés (display:none ou pas dans le document).
  2. Pour <body> et <html>.
  3. Pour les éléments avec position:fixed.

offsetWidth/Height

Passons maintenant à l’élément lui-même.

Ces deux propriétés sont les plus simples. Elles fournissent la largeur/hauteur “extérieure” de l’élément. Ou, en d’autres termes, sa taille complète, y compris les bordures.

Pour notre exemple d’élément :

  • offsetWidth = 390 – la largeur extérieure, peut être calculée comme la largeur CSS intérieure (300px) plus les paddings (2 * 20px) et les bordures (2 * 25px).
  • offsetHeight = 290 – la hauteur extérieure.
Les propriétés de géométrie sont zéro/nulles pour les éléments qui ne sont pas affichés

Les propriétés de géométrie sont calculées uniquement pour les éléments affichés.

Si un élément (ou l’un de ses ancêtres) a display:none ou n’est pas dans le document, alors toutes les propriétés géométriques sont zéro (ou null pour offsetParent).

Par exemple, offsetParent estnull, et offsetWidth,offsetHeight sont 0 lorsque nous avons créé un élément, mais ne l’avons pas encore inséré dans le document, ou il (ou son ancêtre) a display:none.

Nous pouvons l’utiliser pour vérifier si un élément est caché, comme ceci :

function isHidden(elem) {
  return !elem.offsetWidth && !elem.offsetHeight;
}

Veuillez noter qu’un isHidden renvoie true pour les éléments qui sont à l’écran, mais qui ont des tailles nulles (comme une <div> vide).

clientTop/Left

À l’intérieur de l’élément, nous avons les bordures.

Pour les mesurer, il existe des propriétés clientTop et clientLeft.

Dans notre exemple :

  • clientLeft = 25 – largeur de bordure gauche
  • clientTop = 25 – largeur de bordure supérieure

… Mais pour être précis – ces propriétés ne sont pas la largeur/hauteur de la bordure, mais plutôt les coordonnées relatives du côté intérieur par rapport au côté extérieur.

Quelle est la différence ?

Il devient visible lorsque le document est de droite à gauche (le système d’exploitation est en arabe ou en hébreu). La barre de défilement n’est alors pas à droite, mais à gauche, puis clientLeft inclut également la largeur de la barre de défilement.

Dans ce cas, clientLeft ne serait pas 25, mais avec la largeur de la barre de défilement 25 + 16 = 41.

Voici l’exemple en hébreu :

clientWidth/Height

Ces propriétés fournissent la taille de la zone à l’intérieur des bordures des éléments.

Ils incluent la largeur du contenu ainsi que les paddings, mais sans la barre de défilement :

Sur l’image ci-dessus, considérons d’abord clientHeight.

Il n’y a pas de barre de défilement horizontale, c’est donc exactement la somme de ce qui se trouve à l’intérieur des bordures : hauteur CSS 200px plus paddings supérieur et inférieur (2 * 20px) total 240px.

Maintenant clientWidth – ici la largeur du contenu n’est pas 300px, mais 284px, car 16px sont occupés par la barre de défilement. Ainsi, la somme est 284px plus les paddings gauche et droit, total 324px.

S’il n’y a pas de paddings, alors clientWidth/Height est exactement la zone de contenu, à l’intérieur des bordures et de la barre de défilement (le cas échéant).

Donc, quand il n’y a pas de padding, nous pouvons utiliser clientWidth/clientHeight pour obtenir la taille de la zone de contenu.

scrollWidth/Height

Ces propriétés sont comme clientWidth/clientHeight, mais elles incluent également les parties déroulantes (cachées) :

Sur l’image ci-dessus :

  • scrollHeight = 723 – est la pleine hauteur intérieure de la zone de contenu, y compris des parties déroulantes.
  • scrollWidth = 324 – est la largeur intérieure complète, ici nous n’avons pas de défilement horizontal, donc il est égal à clientWidth.

Nous pouvons utiliser ces propriétés pour agrandir l’élément à sa pleine largeur/hauteur.

Comme ceci :

// expand the element to the full content height
element.style.height = `${element.scrollHeight}px`;

Cliquez sur le bouton pour développer l’élément :

text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text

scrollLeft/scrollTop

Les propriétés scrollLeft/scrollTop sont la largeur/hauteur de la partie cachée et déroulée de l’élément.

Sur l’image ci-dessous, nous pouvons voir scrollHeight et scrollTop pour un bloc avec un défilement vertical.

En d’autres termes, scrollTop est “combien est déroulé”.

scrollLeft/scrollTop peut être modifié

La plupart des propriétés de géométrie ici sont en lecture seule, mais scrollLeft/scrollTop peut être modifié, et le navigateur fera défiler l’élément.

Si vous cliquez sur l’élément ci-dessous, le code elem.scrollTop + = 10 s’exécute. Cela fait défiler le contenu de l’élément 10px vers le bas.

Click
Me
1
2
3
4
5
6
7
8
9

Définir scrollTop sur 0 ou une grande valeur, telle que 1e9, fera défiler l’élément vers le haut/bas respectivement.

Ne prenez pas la largeur/hauteur du CSS

Nous venons de couvrir les propriétés géométriques des éléments DOM, qui peuvent être utilisées pour obtenir des largeurs, des hauteurs et calculer des distances.

Mais comme nous le savons du chapitre Styles et classes, nous pouvons lire hauteur et largeur CSS en utilisant getComputedStyle.

Alors pourquoi ne pas lire la largeur d’un élément avec getComputedStyle, comme ceci ?

let elem = document.body;

alert( getComputedStyle(elem).width ); // affiche la largeur CSS pour elem

Pourquoi devrions-nous plutôt utiliser des propriétés géométriques ? Il y a deux raisons :

  1. Tout d’abord, la largeur/hauteur en CSS dépend d’une autre propriété : le box-sizing qui définit “ce qui est” la largeur et la hauteur en CSS. Un changement de box-sizing à des fins CSS peut casser un tel JavaScript.

  2. Deuxièmement, la largeur/hauteur en CSS peut être auto, par exemple pour un élément en ligne :

    <span id="elem">Hello!</span>
    
    <script>
      alert( getComputedStyle(elem).width ); // auto
    </script>

    Du point de vue CSS, width:auto est parfaitement normal, mais en JavaScript, nous avons besoin d’une taille exacte en px que nous pouvons utiliser dans les calculs. Donc ici, la largeur CSS est inutile.

Et il y a une autre raison : une barre de défilement. Parfois, le code qui fonctionne correctement sans barre de défilement devient bogué, car une barre de défilement prend de l’espace dans le contenu de certains navigateurs. La largeur réelle disponible pour le contenu est donc inférieure à la largeur CSS. Et clientWidth/clientHeight en tient compte.

…Mais avec getComputedStyle(elem).width la situation est différente. Certains navigateurs (par exemple Chrome) renvoient la largeur intérieure réelle, moins la barre de défilement, et certains d’entre eux (par exemple Firefox) – largeur CSS (ignorent la barre de défilement). De telles différences entre navigateurs sont la raison de ne pas utiliser getComputedStyle, mais plutôt de s’appuyer sur les propriétés géométriques.

Si votre navigateur réserve l’espace pour une barre de défilement (la plupart des navigateurs pour Windows le font), vous pouvez le tester ci-dessous.

L’élément avec du texte a comme CSS width:300px.

Sur un OS de bureau Windows, Firefox, Chrome, Edge réservent tous l’espace pour la barre de défilement. Mais Firefox affiche 300 pixels, tandis que Chrome et Edge affichent moins. En effet, Firefox renvoie la largeur CSS et les autres navigateurs renvoient la largeur “réelle”.

Veuillez noter que la différence décrite concerne uniquement la lecture de getComputedStyle(...).width à partir de JavaScript, visuellement tout est correct.

Résumé

Les éléments ont les propriétés géométriques suivantes :

  • offsetParent – est l’ancêtre le plus proche ou td, th, table, body.
  • offsetLeft/offsetTop – coordonnées par rapport au bord supérieur gauche de offsetParent.
  • offsetWidth/offsetHeight – largeur/hauteur “extérieure” d’un élément, bordures comprises.
  • clientLeft/clientTop – les distances entre le coin extérieur supérieur gauche et le coin intérieur supérieur gauche (contenu + padding). Pour le système d’exploitation de gauche à droite, ce sont toujours les largeurs des bordures gauche/supérieure. Pour le système d’exploitation de droite à gauche, la barre de défilement verticale est à gauche, donc clientLeft inclut également sa largeur.
  • clientWidth/clientHeight – la largeur/hauteur du contenu, y compris les paddings, mais sans la barre de défilement.
  • scrollWidth/scrollHeight – la largeur/hauteur du contenu, tout comme clientWidth/clientHeight, mais inclut également la partie invisible et déroulante de l’élément.
  • scrollLeft/scrollTop – largeur/hauteur de la partie supérieure déroulante de l’élément, à partir de son coin supérieur gauche.

Toutes les propriétés sont en lecture seule sauf scrollLeft/scrollTop qui font défiler le navigateur l’élément s’il est modifié.

Exercices

importance: 5

La propriété elem.scrollTop est la taille de la partie déroulante à partir du haut. Comment obtenir la taille du défilement inférieur (appelons-le scrollBottom) ?

Écrivez le code qui fonctionne pour un element arbitraire.

P.S. Veuillez vérifier votre code: s’il n’y a pas de défilement ou que l’élément est entièrement défilé vers le bas, alors il devrait retourner 0.

La solution est :

let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;

En d’autres termes : (pleine hauteur) moins (partie supérieure déroulée) moins (partie visible) – c’est exactement la partie inférieure déroulée.

importance: 3

Écrivez le code qui renvoie la largeur d’une barre de défilement standard.

Pour Windows, il varie généralement entre 12px et 20px. Si le navigateur ne lui réserve pas d’espace (la barre de défilement est à moitié translucide sur le texte, cela arrive également), alors il peut s’agir de 0px.

P.S. Le code devrait fonctionner pour tout document HTML, ne dépend pas de son contenu.

Pour obtenir la largeur de la barre de défilement, nous pouvons créer un élément avec le défilement, mais sans bordures ni paddings.

Ensuite, la différence entre sa largeur totale offsetWidth et la largeur de la zone de contenu interne clientWidth sera exactement la barre de défilement :

// créer une div avec le défilement
let div = document.createElement('div');

div.style.overflowY = 'scroll';
div.style.width = '50px';
div.style.height = '50px';

// doit le mettre dans le document, sinon les tailles seront 0
document.body.append(div);
let scrollWidth = div.offsetWidth - div.clientWidth;

div.remove();

alert(scrollWidth);
importance: 5

Voici à quoi ressemble le document source :

Quelles sont les coordonnées du centre de terrain ?

Calculez et utilisez-les pour placer la balle au centre du champ vert :

  • L’élément doit être déplacé par JavaScript, pas CSS.
  • Le code doit fonctionner avec n’importe quelle taille de boule (10, 20, 30 pixels) et n’importe quelle taille de champ, ne pas être lié aux valeurs données.

P.S. Bien sûr, le centrage pourrait être effectué avec CSS, mais ici, nous voulons exactement JavaScript. De plus, nous rencontrerons d’autres sujets et des situations plus complexes lorsque JavaScript doit être utilisé. Ici, nous faisons un “échauffement”.

Open a sandbox for the task.

La balle a une position:absolue. Cela signifie que ses coordonnées gauche/haut sont mesurées à partir de l’élément positionné le plus proche, c’est-à-dire #field (car il a position:relative).

Les coordonnées commencent à partir du coin supérieur gauche intérieur du champ :

La largeur/hauteur du champ intérieur est clientWidth/clientHeight. Le centre de terrain a donc les coordonnées (clientWidth/2, clientHeight/2).

…Mais si nous définissons ball.style.left/top à de telles valeurs, alors pas la balle dans son ensemble, mais le bord supérieur gauche de la balle serait au centre :

ball.style.left = Math.round(field.clientWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2) + 'px';

Voici à quoi ça ressemble :

Pour aligner le centre de la balle avec le centre du terrain, nous devons déplacer la balle sur la moitié de sa largeur à gauche et sur la moitié de sa hauteur vers le haut :

ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';

Maintenant, la balle est enfin centrée.

Attention : l’écueil !

Le code ne fonctionnera pas de manière fiable tant que <img> n’a pas de largeur/hauteur :

<img src="ball.png" id="ball">

Lorsque le navigateur ne connaît pas la largeur/hauteur d’une image (à partir des attributs de balise ou CSS), il suppose qu’ils sont égaux à 0 jusqu’à ce que le chargement de l’image soit terminé.

Ainsi, la valeur de ball.offsetWidth sera 0 jusqu’à ce que l’image se charge. Cela conduit à de mauvaises coordonnées dans le code ci-dessus.

Après le premier chargement, le navigateur met généralement l’image en cache et lors des rechargements, elle aura immédiatement la taille. Mais lors du premier chargement, la valeur de ball.offsetWidth est 0.

Nous devons corriger cela en ajoutant width/height à <img> :

<img src="ball.png" width="40" height="40" id="ball">

…Ou indiquez la taille en CSS :

#ball {
  width: 40px;
  height: 40px;
}

Ouvrez la solution dans une sandbox.

importance: 5

Quelle est la différence entre getComputedStyle(elem).width et elem.clientWidth ?

Donnez au moins 3 différences. Plus c’est mieux.

Différences :

  1. clientWidth est numérique, tandis que getComputedStyle(elem).width renvoie une chaîne de caractères avec px à la fin.
  2. getComputedStyle peut renvoyer une largeur non numérique comme "auto" pour un élément inline.
  3. clientWidth est la zone de contenu interne de l’élément plus les paddings, tandis que la largeur CSS (avec le box-sizing standard ) est la zone de contenu interne sans les paddings.
  4. S’il y a une barre de défilement et que le navigateur lui réserve de l’espace, certains navigateurs soustraient cet espace de la largeur CSS (car il n’est plus disponible pour le contenu), et d’autres non. La propriété clientWidth est toujours la même : la taille de la barre de défilement est soustraite si elle est réservée.
Carte du tutoriel

Commentaires

lire ceci avant de commenter…
  • Si vous avez des améliorations à suggérer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
  • Si vous ne comprenez pas quelque chose dans l'article, merci de préciser.
  • Pour insérer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes -- enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen…)