13 octobre 2020

Coordonnées

Pour déplacer des éléments, nous devons être familiers avec les coordonnées.

La plupart des méthodes JavaScript traitent de l’un des deux systèmes de coordonnées :

  1. Par rapport à la fenêtre – similaire à position:fixed, calculé à partir du bord supérieur/gauche de la fenêtre.
    • nous désignerons ces coordonnées comme clientX/clientY, le raisonnement pour un tel nom deviendra clair plus tard, lorsque nous étudierons les propriétés de l’événement.
  2. Par rapport au document – similaire à position:absolue à la racine du document, calculé à partir du bord supérieur/gauche du document.
    • nous les dénotons pageX/pageY.

Lorsque la page défile jusqu’au tout début, de sorte que le coin supérieur/gauche de la fenêtre soit exactement le coin supérieur/gauche du document, ces coordonnées sont égales. Mais après le déplacement du document, les coordonnées relatives à la fenêtre des éléments changent, à mesure que les éléments se déplacent à travers la fenêtre, tandis que les coordonnées relatives au document restent les mêmes.

Sur cette image, nous prenons un point dans le document et démontrons ses coordonnées avant le défilement (à gauche) et après (à droite) :

Lorsque le document défile :

  • pageY – La coordonnée relative au document est restée la même, elle est comptée à partir du haut du document (maintenant défilée).
  • clientY – la coordonnée relative à la fenêtre a changé (la flèche est devenue plus courte), car le même point s’est rapproché du haut de la fenêtre.

Coordonnées des éléments : getBoundingClientRect

La méthode elem.getBoundingClientRect() renvoie les coordonnées de la fenêtre pour un rectangle minimal qui entoure elem en tant qu’objet de la classe intégrée DOMRect.

Propriétés principales de DOMRect :

  • x/y – Coordonnées X/Y de l’origine du rectangle par rapport à la fenêtre,
  • width/height – largeur/hauteur du rectangle (peut être négatif).

De plus, il existe des propriétés dérivées :

  • top/bottom – Coordonnée Y pour le bord supérieur/inférieur du rectangle,
  • left/right – Coordonnée X pour le bord du rectangle gauche/droit.

Par exemple, cliquez sur ce bouton pour voir les coordonnées de sa fenêtre :

Si vous faites défiler la page et répétez, vous remarquerez que lorsque la position du bouton relatif à la fenêtre change, ses coordonnées de fenêtre (y/top/bottom si vous faites défiler verticalement) changent également.

Voici l’image de la sortie de elem.getBoundingClientRect() :

Comme vous pouvez le voir, x/y et width/height décrivent entièrement le rectangle. Les propriétés dérivées peuvent être facilement calculées à partir d’eux :

  • left = x
  • top = y
  • right = x + width
  • bottom = y + height

Veuillez noter :

  • Les coordonnées peuvent être des fractions décimales, telles que 10.5. C’est normal, le navigateur utilise en interne des fractions dans les calculs. Nous n’avons pas à les arrondir lors de la définition de style.left/top.
  • Les coordonnées peuvent être négatives. Par exemple, si la page défile de sorte que elem se trouve maintenant au-dessus de la fenêtre, alors elem.getBoundingClientRect().top est négatif.
Pourquoi des propriétés dérivées sont nécessaires ? Pourquoi top/left existent-ils s’il y a x/y ?

Mathématiquement, un rectangle est défini de façon unique avec son point de départ (x,y) et le vecteur de direction (width,height). Les propriétés dérivées supplémentaires sont donc pour plus de commodité.

Techniquement, il est possible que width/height soit négatif, ce qui permet un rectangle “dirigé”, par exemple pour représenter la sélection de la souris avec un début et une fin correctement marqués.

Les valeurs négatives de width/height signifient que le rectangle commence à son coin inférieur droit puis “grandit” de gauche à droite.

Voici un rectangle avec une width et height (par exemple width=-200, height=-100) :

Comme vous pouvez le voir, left/top n’est pas égal à x/y dans ce cas.

En pratique cependant, elem.getBoundingClientRect() retourne toujours une largeur/hauteur positive, ici nous mentionnons une largeur/hauteur négative uniquement pour que vous compreniez pourquoi ces propriétés apparemment en double ne sont pas en fait des doublons.

Internet Explorer : pas de support pour x/y

Internet Explorer ne prend pas en charge les propriétés x/y pour des raisons historiques.

Nous pouvons donc soit faire un polyfill (ajouter des getters dans DomRect.prototype), soit simplement utiliser top/left, car ils sont toujours les mêmes que x/y pour un width/height positif, en particulier dans le résultat de elem.getBoundingClientRect().

Les coordonnées droite / inférieure sont différentes des propriétés de position CSS

Il existe des similitudes évidentes entre les coordonnées relatives à la fenêtre et CSS position:fixed.

Mais dans le positionnement CSS, la propriété right signifie la distance par rapport au bord droit, et la propriété bottom signifie la distance par rapport au bord inférieur.

Si nous regardons simplement l’image ci-dessus, nous pouvons voir qu’en JavaScript, ce n’est pas le cas. Toutes les coordonnées de la fenêtre sont comptées à partir du coin supérieur gauche, y compris celles-ci.

elementFromPoint(x, y)

L’appel à document.elementFromPoint(x,y) renvoie l’élément le plus imbriqué aux coordonnées de la fenêtre (x,y).

La syntaxe est :

let elem = document.elementFromPoint(x, y);

Par exemple, le code ci-dessous met en évidence et génère la balise de l’élément qui se trouve maintenant au milieu de la fenêtre :

let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;

let elem = document.elementFromPoint(centerX, centerY);

elem.style.background = "red";
alert(elem.tagName);

Comme il utilise les coordonnées de la fenêtre, l’élément peut être différent selon la position de défilement actuelle.

Pour les coordonnées hors fenêtre, elementFromPoint renvoie null

La méthode document.elementFromPoint(x,y) ne fonctionne que si (x,y) se trouve à l’intérieur de la zone visible.

Si l’une des coordonnées est négative ou dépasse la largeur/hauteur de la fenêtre, elle renvoie alors null.

Voici une erreur typique qui peut se produire si nous ne la vérifions pas :

let elem = document.elementFromPoint(x, y);
// si les coordonnées se trouvent hors de la fenêtre, alors elem = null
elem.style.background = ''; // Error!

Utilisation pour un positionnement “fixe”

La plupart du temps, nous avons besoin de coordonnées pour positionner quelque chose.

Pour afficher quelque chose près d’un élément, nous pouvons utiliser getBoundingClientRect pour obtenir ses coordonnées, puis position CSS avec left/top (ou right/bottom).

Par exemple, la fonction createMessageUnder(elem, html) ci-dessous affiche le message sous elem :

let elem = document.getElementById("coords-show-mark");

function createMessageUnder(elem, html) {
  // créer un élément de message
  let message = document.createElement('div');
  // mieux utiliser une classe css pour le style ici
  message.style.cssText = "position:fixed; color: red";

  // attribuez des coordonnées, n'oubliez pas "px" !
  let coords = elem.getBoundingClientRect();

  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}

// Utilisation :
// l'ajouter pendant 5 secondes dans le document
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);

Cliquez sur le bouton pour l’exécuter :

Le code peut être modifié pour afficher le message à gauche, à droite, en dessous, appliquer des animations CSS pour un “fondu” et ainsi de suite. C’est facile, car nous avons toutes les coordonnées et tailles de l’élément.

Mais notez le détail important : lorsque la page défile, le message s’éloigne du bouton.

La raison en est évidente: l’élément de message repose sur position:fixed, il reste donc au même endroit de la fenêtre pendant que la page défile.

Pour changer cela, nous devons utiliser des coordonnées basées sur des documents et position:absolute.

Coordonnées du document

Les coordonnées relatives au document commencent dans le coin supérieur gauche du document, pas dans la fenêtre.

En CSS, les coordonnées de la fenêtre correspondent à position:fixed, tandis que les coordonnées du document sont similaires à position:absolue en haut.

Nous pouvons utiliser position:absolu et top/left pour placer quelque chose à un certain endroit du document, afin qu’il y reste pendant un défilement de page. Mais nous avons d’abord besoin des bonnes coordonnées.

Il n’y a pas de méthode standard pour obtenir les coordonnées du document d’un élément. Mais c’est facile de l’écrire.

Les deux systèmes de coordonnées sont reliés par la formule :

  • pageY = clientY + hauteur de la partie verticale déroulée du document.
  • pageX = clientX + largeur de la partie horizontale déroulée du document.

La fonction getCoords(elem) prendra les coordonnées de la fenêtre de elem.getBoundingClientRect() et leur ajouter le défilement actuel :

// obtenir les coordonnées du document de l'élément
function getCoords(elem) {
  let box = elem.getBoundingClientRect();

  return {
    top: box.top + window.pageYOffset,
    right: box.right + window.pageXOffset,
    bottom: box.bottom + window.pageYOffset,
    left: box.left + window.pageXOffset
  };
}

Si dans l’exemple ci-dessus, nous l’avons utilisé avec position:absolue, le message resterait près de l’élément en défilement.

La fonction createMessageUnder modifiée :

function createMessageUnder(elem, html) {
  let message = document.createElement('div');
  message.style.cssText = "position:absolute; color: red";

  let coords = getCoords(elem);

  message.style.left = coords.left + "px";
  message.style.top = coords.bottom + "px";

  message.innerHTML = html;

  return message;
}

Résumé

N’importe quel point de la page a des coordonnées :

  1. Par rapport à la fenêtre – elem.getBoundingClientRect().
  2. Par rapport au document – elem.getBoundingClientRect() plus le défilement de la page actuelle.

Les coordonnées de la fenêtre sont excellentes à utiliser avec position:fixed, et les coordonnées du document fonctionnent bien avec position:absolue.

Les deux systèmes de coordonnées ont leurs avantages et leurs inconvénients; il y a des moments où nous avons besoin de l’un ou de l’autre, tout comme position CSS absolute et fixed.

Exercices

importance: 5

Dans l’iframe ci-dessous, vous pouvez voir un document avec le “champ” vert.

Utilisez JavaScript pour trouver les coordonnées de la fenêtre des coins pointés par des flèches.

Il y a une petite fonctionnalité implémentée dans le document pour plus de commodité. Un clic à n’importe quel endroit montre les coordonnées là-bas.

Votre code doit utiliser DOM pour obtenir les coordonnées de la fenêtre de :

  1. Coin extérieur supérieur gauche (c’est simple).
  2. En bas à droite, coin extérieur (simple aussi).
  3. Coin intérieur supérieur gauche (un peu plus dur).
  4. En bas à droite, coin intérieur (il y a plusieurs façons, choisissez-en une).

Les coordonnées que vous calculez doivent être les mêmes que celles renvoyées par le clic de souris.

P.S. Le code devrait également fonctionner si l’élément a une autre taille ou bordure, qui n’est lié à aucune valeur fixe.

Open a sandbox for the task.

Coins extérieurs

Les coins extérieurs sont essentiellement ce que nous obtenons de elem.getBoundingClientRect().

Les coordonnées du coin supérieur gauche answer1 et du coin inférieur droitanswer2 :

let coords = elem.getBoundingClientRect();

let answer1 = [coords.left, coords.top];
let answer2 = [coords.right, coords.bottom];

Coin intérieur supérieur gauche

Cela diffère du coin extérieur par la largeur de la bordure. Un moyen fiable pour obtenir la distance est clientLeft/clientTop :

let answer3 = [coords.left + field.clientLeft, coords.top + field.clientTop];

Coin intérieur en bas à droite

Dans notre cas, nous devons soustraire la taille de la bordure des coordonnées extérieures.

Nous pourrions utiliser la manière CSS :

let answer4 = [
  coords.right - parseInt(getComputedStyle(field).borderRightWidth),
  coords.bottom - parseInt(getComputedStyle(field).borderBottomWidth)
];

Une autre façon serait d’ajouter clientWidth/clientHeight aux coordonnées du coin supérieur gauche. C’est probablement encore mieux :

let answer4 = [
  coords.left + elem.clientLeft + elem.clientWidth,
  coords.top + elem.clientTop + elem.clientHeight
];

Ouvrez la solution dans une sandbox.

importance: 5

Créez une fonction positionAt(anchor, position, elem) qui positionne elem, en fonction de position près de l’élément anchor.

La position doit être une chaîne de caractères avec l’une des 3 valeurs :

  • "top" – position elem juste au dessus de anchor
  • "right" – position elem immédiatement à droite de anchor
  • "bottom" – position elem juste en dessous anchor

Il est utilisé à l’intérieur de la fonction showNote(anchor, position, html), fournie dans le code source de la tâche, qui crée un élément “note” avec html donné et l’affiche à la position donnée près de anchor.

Voici la démo des notes :

Open a sandbox for the task.

Dans cet exercice, il suffit de calculer avec précision les coordonnées. Voir le code pour plus de détails.

Veuillez noter : les éléments doivent être dans le document pour lire offsetHeight et d’autres propriétés. Un élément caché (display:none) ou hors du document n’a pas de taille.

Ouvrez la solution dans une sandbox.

importance: 5

Modifier la solution du précédent exercice de sorte que la note utilise position:absolute au lieu de position:fixed.

Cela empêchera son “éloignement” de l’élément lorsque la page défile.

Prenez la solution de cet exercice comme point de départ. Pour tester le défilement, ajoutez le style <body style="height: 2000px">.

La solution est en fait assez simple :

  • Utilisez position:absolute dans le CSS au lieu de position:fixed pour .note.
  • Utilisez la fonction getCoords() du chapitre Coordonnées pour obtenir les coordonnées relatives au document.

Ouvrez la solution dans une sandbox.

importance: 5

Étendre l’exercice précédent Afficher une note près de l'élément (absolute) : enseigner la fonction positionAt(anchor, position, elem) pour inserer elem dans le anchor.

Nouvelles valeurs pour position :

  • top-out, right-out, bottom-out – fonctionnent de la même manière qu’avant, ils insèrent le elem au-dessus/à droite/sous le anchor.
  • top-in, right-in, bottom-in – insèrent elem à l’intérieur de anchor : collez-le sur le bord supérieur/droit/inférieur.

Par exemple :

// affiche la note au dessus de blockquote
positionAt(blockquote, "top-out", note);

// affiche la note à l'intérieur de blockquote, en haut
positionAt(blockquote, "top-in", note);

Le resultat :

En tant que code source, prenez la solution de l’exercice Afficher une note près de l'élément (absolute).

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…)