On my website, a related content box should be animated into the viewport when it gets visible.
I’m trying to make my animation as efficient as possible through CSS and JavaScript, so that it doesn’t affects scroll performance negatively.
While the CSS part was simple (using transform, will-change, contain), I’m struggling a bit with when to use window.requestAnimationFrame
.
Should I use it only when the class is added to the element or also when the function isScrolledIntoView
is called or even inside isScrolledIntoView
, when the elements position is measured?
var percentVisible = 0.25;
window.addEventListener('scroll', function(){
relatedContent(related, percentVisible);
}
)
function relatedContent(r, pV){
window.requestAnimationFrame(function() {
if(isScrolledIntoView(r, pV)){
window.requestAnimationFrame(function(){
r.classList.add("visible");
}, r)
}
}, r)
}
function isScrolledIntoView(el, percentV) {
var elemTop, elemBottom, elemHeight, overhang, isVisible;
/*window.requestAnimationFrame(
function(){*/
elemTop = el.getBoundingClientRect().top;
elemBottom = el.getBoundingClientRect().bottom;
elemHeight = el.getBoundingClientRect().height;
/*}
);*/
overhang = elemHeight * (1 - percentV);
isVisible = (elemTop >= -overhang) && (elemBottom <= window.innerHeight + overhang);
return isVisible;
}
No don't use it like that...
- requestAnimationFrame (rAF) is a timing function that does synchronize with the screen refresh rate (generally 60fps).
- Scroll event may fire more often than 60 events per second.
- Each call to rAF will stack all the functions passed as its parameter in some kind of a big function called just before the next screen refresh.
Combine all of this and what you get is multiple calls to the same function in a stack, just before the next screen refresh.
Instead, what you seem to want is to prevent your scroll event to fire when not useful. This is called a throttle function, and you're a bit far from it.
Here is a simple throttle implementation using rAF :
var throttle = function(callback) {
var active = false; // a simple flag
var evt; // to keep track of the last event
var handler = function(){ // fired only when screen has refreshed
active = false; // release our flag
callback(evt);
}
return function handleEvent(e) { // the actual event handler
evt = e; // save our event at each call
if (!active) { // only if we weren't already doing it
active = true; // raise the flag
requestAnimationFrame(handler); // wait for next screen refresh
};
}
}
That you could use like this :
window.addEventListener('scroll', throttle(yourScrollCallback));
requestAnimationFrame
returns a non-zero long
that can be used to cancel your request, so instead of writing your own throttle implementation, you can use the following simpler approach to prevent multiple handlers stacking up:
let currentRequest;
document.addEventListener('scroll', function () {
cancelAnimationFrame(currentRequest);
currentRequest = requestAnimationFrame(handleScroll);
});