I need to add positon: fixed to .box only if the user scrolls the page.
The important point here is that the element with the class .box get generated only after the user scrolls the page.
This is what I came up with:
window.addEventListener('scroll', () => {
const myDiv = document.querySelector('.box')
if (myDiv) {
if (myDiv.style.position !== 'fixed') {
myDiv.style.position = 'fixed'
}
}
})
The problem with my code is that the scroll event is now going to fire a million events all the time and kill performance.
What would be the right way to achieve the above without firing scroll event again and again.
After the box has been inserted into the DOM, you can query the box and add the fixed position style to it.
If you're doing this often then here is a more general function that will (given a selector, e.g. .box) wait for an element to be inserted into the DOM:
function waitForElement(selector) {
let element = null;
let handled = false;
let nextFrame;
return (callback) => {
function tick() {
element = document.querySelector(selector);
if (element === null) {
nextFrame = requestAnimationFrame(tick);
}
if (element !== null && !handled) {
handled = true;
callback(element);
}
}
cancelAnimationFrame(nextFrame);
requestAnimationFrame(tick);
};
}
Usage:
const waitForBox = waitForElement(".box");
waitForBox((box) => {
box.style.position = "fixed";
});
Demo:
// Demo section, ignore this
const plane = document.querySelector(".plane");
const box = document.createElement("div");
box.className = "box";
box.style.width = "100px";
box.style.height = "100px";
box.style.background = "crimson";
function waitForElement(selector) {
let element = null;
let handled = false;
let nextFrame;
return (callback) => {
function tick() {
element = document.querySelector(selector);
if (element === null) {
nextFrame = requestAnimationFrame(tick);
}
if (element !== null && !handled) {
handled = true;
callback(element);
}
}
cancelAnimationFrame(nextFrame);
requestAnimationFrame(tick);
};
}
// A function to call inside scroll callback that will
// wait for the .box element to be found in the DOM
const waitForBox = waitForElement(".box");
let boxAppended = false;
document.addEventListener("scroll", () => {
// Appending the box to the DOM, ignore this
if (!boxAppended) {
boxAppended = true;
plane.appendChild(box);
}
// Callback will be fired once when the .box
// element is found in the DOM
waitForBox((box) => {
box.style.position = "fixed";
});
});
body {
padding: 0;
height: 100vh;
overflow: auto;
}
.plane {
height: 200vh;
}
<div class="plane"></div>
You can for example use requestAnimationFrame() or setTimeout() to prevent the scroll event from firing too many times.
Here is an example of how to do this with requestAnimationFrame() from the Mozilla scroll event documentation:
let lastKnownScrollPosition = 0;
let ticking = false;
function doSomething(scrollPos) {
// Do something with the scroll position
}
document.addEventListener('scroll', function(e) {
lastKnownScrollPosition = window.scrollY;
if (!ticking) {
window.requestAnimationFrame(function() {
doSomething(lastKnownScrollPosition);
ticking = false;
});
ticking = true;
}
});