Gergő Horváth
Gergő Horváth

Reputation: 3705

How to check if an HTMLElement is fully loaded

Run this code:

window.onload = function () {
  const sections = document.querySelectorAll("section");
  let eT = [];
  for (let i = 0, len = sections.length; i < len; i++) {
      const el = sections[i];
      if (el.parentNode.className !== "wrapper") {
          const wrapper = document.createElement("div");
          wrapper.className = "wrapper";
          el.parentNode.appendChild(wrapper);
          wrapper.appendChild(el);
      }
      const elCont = document.querySelectorAll(".wrapper")[i];
      eT[i] = elCont.offsetTop;
      setTimeout(() => {
          console.log(eT[i], elCont.offsetTop)
      }, 100);
  }
}
section{
  width: 100vw;
  height: 1000px;
  position: relative;
  border: 1px solid;
}
body{
  position: relative;
}
<body>
<section></section>
<section></section>
<section></section>
<section></section>
</body>

As you can see, the DOM manipulations, then reading them offsetTops happen too quick, so if i read they offsetTops immediately after creating them, adding them to the body, then adding the sections to them as childs, the values are wrong. But if i wait 100ms, the values are correct. Okay, setting a timeout would be an option to solve the problem, but i think there must be a solution which is more elegant than this. Someone knows this solution?

Upvotes: 2

Views: 132

Answers (2)

Lain
Lain

Reputation: 3726

You could also use the MutationObserver for this, since it will get trigged after the elements have been added to the DOM (here .wrapper).

window.onload = function(){
  const sections = document.querySelectorAll("section");

  //https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
  var tObserver = new MutationObserver(function(mutations){
    if(mutations){
      for(let i=0, len=sections.length; i<len; i++){
        console.log('MutationObserver', sections[i].offsetTop)
      };

      this.disconnect()
    }
  });

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

  let eT = [];
  for(let i=0, len=sections.length; i<len; i++){
    const el = sections[i];
    if(el.parentNode.className !== "wrapper"){
      const wrapper = document.createElement("div");
      wrapper.className = "wrapper";
      el.parentNode.appendChild(wrapper);
      wrapper.appendChild(el);
    };

    const elCont = document.querySelectorAll(".wrapper")[i];
    eT[i] = elCont.offsetTop;
    console.log('in for loop', elCont.offsetTop);

    setTimeout(() => {
      console.log('in timeout', elCont.offsetTop)
    }, 100)
  }
}
section{
  border: 1px solid;
  height: 1000px;
  position: relative;
  width: 100vw
}

body{
  position: relative
}
<section></section>
<section></section>
<section></section>
<section></section>

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1074266

Yielding back to the browser (setTimeout is one way, requestAnimationFrame is another) is indeed how you do this. It's so that you allow the browser time to render the result, which happens sometime after the task in which you've added the elements completes (in your case, the task that ran your load event handler).

setTimeout with a timeout of 0 works in most cases; I've had to use values between 60 and 100 on Firefox in the past, but test in your target environments. You might also play around with requestAnimationFrame callbacks, which happen much sooner than 100ms (provided the browser isn't blocked on something):

window.onload = function () {
  const sections = document.querySelectorAll("section");
  let eT = [];
  for (let i = 0, len = sections.length; i < len; i++) {
      const el = sections[i];
      if (el.parentNode.className !== "wrapper") {
          const wrapper = document.createElement("div");
          wrapper.className = "wrapper";
          el.parentNode.appendChild(wrapper);
          wrapper.appendChild(el);
      }
      const elCont = document.querySelectorAll(".wrapper")[i];
      eT[i] = elCont.offsetTop;
      requestAnimationFrame(() => {
          console.log(eT[i], elCont.offsetTop)
      });
  }
}
section{
  width: 100vw;
  height: 1000px;
  position: relative;
  border: 1px solid;
}
body{
  position: relative;
}
<body>
<section></section>
<section></section>
<section></section>
<section></section>
</body>

That example works for me on Firefox, Chrome, and Edge ("works for me" => I see 0, 1002, 2004, and 3006 for the offsetTops in the rAF callback).

Upvotes: 2

Related Questions