Reputation: 3705
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
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
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 offsetTop
s in the rAF callback).
Upvotes: 2