Home > Software engineering >  How to wait for appendChild() to update and style the DOM, before trying to measure the new element&
How to wait for appendChild() to update and style the DOM, before trying to measure the new element&

Time:05-03

I'm appending a DOM element like this:

this.store.state.runtime.UIWrap.appendChild(newElement)

When I immediately try to measure the new element's width I get 2.

I tried:

  • setTimeout()
  • double nested window.requestAnimationFrame()
  • MutationObserver

The above works very unreliably, like 50% of the time. Only when I set a large timeout like 500ms it worked.

This happens only on mobile.

This is the workaround that I'm using, but it's ugly:

function getWidthFromStyle(el) {
  return parseFloat(getComputedStyle(el, null).width.replace('px', ''))
}
function getWidthFromBoundingClientRect(el) {
  return el.getBoundingClientRect().width
}

console.log(getWidthFromBoundingClientRect(newElement))

while (getWidthFromBoundingClientRect(newElement) < 50) {
  await new Promise(r => setTimeout(r, 500))
  console.log(getWidthFromBoundingClientRect(newElement))
}

I tried with both functions getWidthFromStyle() and getWidthFromBoundingClientRect() - no difference. The width gets calculated properly after a couple of cycles.

I also tried using MutationObserver without success.

Is there a way to know when the DOM is fully updated and styled before I try to measure an element's width/height?

P.S. I'm not using any framework. this.store.state.runtime... is my own implementation of a Store, similar to Vue.

EDIT: The size of the element depended on an image inside it and I was trying to measure the element before the image had loaded. Silly.

CodePudding user response:

You can use something like this:

export function waitElement(elementId) {
  return new Promise((resolve, reject) => {
    const element = document.getElementById(elementId);
    if (element) {
      resolve(element);
    } else {
      let tries = 10;
      const interval = setInterval(() => {
        const element = document.getElementById(elementId);
        if (element) {
          clearInterval(interval);
          resolve(element);
        }
        if (tries-- < 0) {
          clearInterval(interval);
          reject(new Error("Element not found"));
        }
      }, 100);
    }
  });
}

CodePudding user response:

it can done with MutationObserver.
doesn't this method solve your problem?

const div = document.querySelector("div");
const span = document.querySelector("span");

const observer = new MutationObserver(function () {
  console.log("new width", size());
});

observer.observe(div, { subtree: true, childList: true });

function addElem() {
  setTimeout(() => {
    const newSpan = document.createElement("span");
    newSpan.innerHTML = "second";
    div.appendChild(newSpan);
    console.log("element added");
  }, 3000);
}

function size() {
  return div.getBoundingClientRect().width;
}

console.log("old width", size());
addElem();
div {
  display: inline-block;
  border: 1px dashed;
}
span {
  background: gold;
}
<div>
  <span>one</span>
</div>

  • Related