60fps: How to use requestAnimationFrame the right

2019-01-27 05:15发布

问题:

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;
}

回答1:

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));


回答2:

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);
});