HC_ HC_ - 5 months ago 14
CSS Question

Should I use requestAnimationFrame to fade in several elements? To achieve 60fps animation?

Trying to maintain 60 fps animation. Currently, I'm getting a lot of spikes of slow frames that are happening at like 30~ fps and the choppiness is noticeable to my eye.

Significant Edits: Throwing out old obsolete code, adding in new code with explanation

fadeIn: function(ele){
raf = window.requestAnimationFrame(function() {
console.log(ele);
var opacity = 0;
function increase () {
opacity += 0.05;
if (opacity >= 1){
// complete
ele.style.opacity = 1;
return true;
}
ele.style.opacity = opacity;
requestAnimationFrame(increase);
}
increase();
});
},

fadeInElements: function(elements, properties, speed, delay){
var raf;
var ele;

for (i = 0; i < properties.length; i++){
ele = elements[properties[i]];
console.log('outside loop ' + ele);
instance.fadeIn(ele);
}
},


My new code is above. It is successfully:


  • Iterating through several elements (each as
    ele
    ) and then calling
    fadeIn(ele)

  • So, all elements fade in.

  • However, I want a
    50ms
    delay between each "fade in" (each triggering of
    fadeIn()
    on a new element


cxw cxw
Answer

The good news is that it's not actually recursion — it's more like a timeout. You provide a function that draws a frame, and the browser calls it for you.

Here's an answer showing excerpts from a complete JSFiddle. It doesn't try to replicate your exact code, but instead tries to explain what you need to know so you can adapt your code accordingly. The code was written to be easy to understand, so I'm sure there are other ways to do it faster!

This answer works from the top level down, which means I describe the end of the JSFiddle source and work my way backwards. I personally think this makes it easier to understand than does starting with the details.

  1. You need to start the animation somewhere. So the very last thing in the JSFiddle is

    window.requestAnimationFrame(eachFrame);    //start the animation
    

    This will call a function called eachFrame() when it's time for the next frame, e.g,. on the next multiple of 60 times per second. It will only do it once, though.

  2. You need eachFrame() to keep track of where we are in the animation.

    var startTime = -1.0;  // -1 = a flag for the first frame.
    function eachFrame()
    {
      // Render this frame ------------------------
      if(startTime<0) {
        // very first frame (because of the -1.0): save the start time.
        startTime = (new Date()).getTime();
        render(0.0);
          // the parameter to render() is the time within the
          // animation.
      } else {
        // every frame after the first: subtract the saved startTime
        // to determine the current time.
        render( (new Date()).getTime() - startTime );
      }
    
      // Now we're done rendering one frame. ------
      //Start the timer to call this function again
      //when it's time for the next frame.
      window.requestAnimationFrame(eachFrame);  
    
    }; //eachFrame
    

    eachFrame() determines what the current time is with respect to the beginning of the animation. getTime() gives you the time in milliseconds.

    The other thing eachFrame() does is to call window.requestAnimationFrame(eachFrame); again. This isn't recursion. Instead, eachFrame() will finish running, and then after that, the next time a frame comes around, the browser will call eachFrame() again.

  3. The last function you need is something to actually draw the frame! That is render(current time). Assume that, e.g., head1 and head2 refer to two heading elements you want to animate, e.g., <h1> elements declared in your HTML. The clamp(x) function returns x but clamped below at 0 and above at 1.

    function render(currTime)
    { // *** Put your rendering code here ***
      // How opaque should head1 be?  Its fade started at currTime=0.
      var opacity1 = clamp(currTime/FADE_DURATION);
        // over FADE_DURATION ms, opacity goes from 0 to 1
    
      // How opaque should head2 be?
      var opacity2 = clamp( (currTime-FADE_SPACING)/FADE_DURATION );
        // fades in, but doesn't start doing it until
        // FADE_SPACING ms have passed.
    
      // Apply the changes
      head1.style.opacity = opacity1;
      head2.style.opacity = opacity2;
    } //render
    

    In render(), you figure out opacity based on the current time. You don't have to worry about delaying between frames, because requestAnimationFrame handles that for us. You stagger the transitions by offsetting the time. In this example, opacity1 depends on currTime and opacity2 depends on currTime minus a constant FADE_SPACING, so the opacity change for element 2 will start later than the opacity change for element 1 by FADE_SPACING ms.

The JSFiddle has all the details filled in. It animates the opacity of two <h1> elements, with a spacing between the beginning of the animation for each element. I hope this helps!

Comments