105 votes

Y a-t-il un événement de rendu d'élément?

Je dois mesurer avec précision les dimensions du texte au sein de mon application Web, ce que j'obtiens en créant un élément (avec des classes CSS pertinentes), en définissant son innerHTML puis en l'ajoutant au conteneur en utilisant appendChild.

Après avoir fait cela, il y a une attente avant que l'élément ne soit rendu et que son offsetWidth puisse être lu pour savoir quelle est la largeur du texte.

Actuellement, j'utilise setTimeout(processText, 100) pour attendre que le rendu soit complet.

Y a-t-il un rappel auquel je peux écouter, ou un moyen plus fiable de savoir quand un élément que j'ai créé a été rendu?

90voto

Elliot B. Points 4236

Lorsque un élément est ajouté au DOM, vous pourrez récupérer ses dimensions réelles.

Voici un exemple simple de la façon dont vous pouvez utiliser un MutationObserver pour écouter quand un élément est ajouté au DOM.

Pour des raisons de brièveté, j'utilise la syntaxe jQuery pour construire le nœud et l'insérer dans le DOM.

var monElement = $("hello world")[0];

var observateur = new MutationObserver(function(mutations) {
   if (document.contains(monElement)) {
        console.log("Il est dans le DOM!");
        observateur.disconnect();
    }
});

observateur.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});

$("body").append(monElement); // console.log: Il est dans le DOM!

L'événement observateur se déclenchera chaque fois qu'un nœud est ajouté ou supprimé du document. À l'intérieur du gestionnaire, nous effectuons ensuite une vérification contains pour déterminer si monElement est maintenant dans le document.

Vous n'avez pas besoin de parcourir chaque MutationRecord stocké dans mutations car vous pouvez effectuer la vérification document.contains directement sur monElement.

Pour améliorer les performances, remplacez document par l'élément spécifique qui contiendra monElement dans le DOM.

62voto

mystrdat Points 975

Il n'existe actuellement aucun événement DOM indiquant qu'un élément a été entièrement rendu (par exemple, que le CSS a été appliqué et affiché). Cela peut entraîner des erreurs ou des résultats aléatoires dans du code de manipulation du DOM (comme obtenir la hauteur d'un élément).

Utiliser setTimeout pour donner au navigateur un temps supplémentaire pour le rendu est la façon la plus simple. Utiliser

setTimeout(function(){}, 0)

est peut-être la manière la plus précisément pratique, car cela place votre code à la fin de la file d'événements active du navigateur sans aucun délai supplémentaire - en d'autres termes, votre code est mis en file d'attente juste après l'opération de rendu (et toutes les autres opérations se produisant à ce moment-là).

24voto

Yuval A. Points 931

Ce billet de blog de Swizec Teller, suggère d'utiliser requestAnimationFrame, et de vérifier la taille de l'élément.

function try_do_some_stuff() {
    if (!$("#element").size()) {
        window.requestAnimationFrame(try_do_some_stuff);
    } else {
        $("#element").do_some_stuff();
    }
};

en pratique, cela ne retente qu'une seule fois. Parce que peu importe ce qui se passe, au prochain rendu, que ce soit dans 1/60ème de seconde ou une minute, l'élément aura été rendu.

Vous devez effectivement attendre encore un peu après pour obtenir le temps de rendu après. requestAnimationFrame se déclenche avant le prochain affichage. Donc requestAnimationFrame(()=>setTimeout(onrender, 0)) est juste après que l'élément a été rendu.

17voto

Robbendebiene Points 1721

Dans mon cas, des solutions comme setTimeout ou MutationObserver n'étaient pas totalement fiables. Au lieu de cela, j'ai utilisé le ResizeObserver. Selon MDN:

Les implémentations devraient, si elles suivent la spécification, déclencher des événements de redimensionnement avant le rendu et après la mise en page.

En gros, l'observateur se déclenche toujours après la mise en page, donc nous devrions pouvoir obtenir les bonnes dimensions de l'élément observé. En bonus, l'observateur renvoie déjà les dimensions de l'élément. Par conséquent, nous n'avons même pas besoin d'appeler quelque chose comme offsetWidth (même si cela devrait fonctionner aussi).

const myElement = document.createElement("div");
myElement.textContent = "chaîne de test";

const resizeObserver = new ResizeObserver(entries => {
  const lastEntry = entries.pop();

  // utiliser contentBoxSize ici en alternative
  // Remarque : les anciennes versions de Firefox (<= 91) ont fourni un objet de taille unique au lieu d'un tableau de tailles
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1689645
  const width = lastEntry.borderBoxSize?.inlineSize ?? lastEntry.borderBoxSize[0].inlineSize;
  const height = lastEntry.borderBoxSize?.blockSize ?? lastEntry.borderBoxSize[0].blockSize;

  resizeObserver.disconnect();

  console.log("largeur :", largeur, "hauteur :", hauteur);
});

resizeObserver.observe(myElement);

document.body.append(myElement);

Cela peut également être enveloppé dans une fonction asynchrone pratique comme ceci:

function appendAwaitLayout(parent, element) {
  return new Promise((resolve, reject) => {
    const resizeObserver = new ResizeObserver((entries) => {
      resizeObserver.disconnect();
      resolve(entries);
    });

    resizeObserver.observe(element);

    parent.append(element);
  });
}

// appelez-le de cette manière
appendAwaitLayout(document.body, document.createElement("div")).then((entries) => {
  console.log(entries)
  // faire des choses ici ...
});

9voto

hanksterr7 Points 99

Le MutationObserver est probablement la meilleure approche, mais voici une alternative simple qui pourrait fonctionner

J'avais du javascript qui construisait le HTML pour un grand tableau et définissait l'innerHTML d'une div avec le HTML généré. Si je récupérais Date() immédiatement après avoir défini l'innerHTML, je trouvais que le timestamp était pour un temps antérieur à ce que le tableau était totalement rendu. Je voulais savoir combien de temps le rendu prenait (ce qui signifie que je devais vérifier Date() après que le rendu soit terminé). J'ai trouvé que je pouvais le faire en définissant l'innerHTML de la div puis (dans le même script) en appelant la méthode click d'un bouton sur la page. Le gestionnaire de clic serait exécuté uniquement après que le HTML soit entièrement rendu, pas seulement après que la propriété innerHTML de la div soit définie. J'ai vérifié cela en comparant la valeur Date() générée par le gestionnaire de clic à la valeur Date() récupérée par le script qui définissait la propriété innerHTML de la div.

J'espère que quelqu'un trouvera cela utile

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X