metafizzy / flickity

:leaves: Touch, responsive, flickable carousels
https://flickity.metafizzy.co
7.5k stars 604 forks source link

Flickity animations take forever if your browser is configured to throttle frame rate (and hence requestAnimationFrame callbacks) #1305

Open dholbert opened 2 months ago

dholbert commented 2 months ago

Test case: https://codepen.io/dholbert/pen/XWwbrRK (This is just the default testcase, which I forked & didn't modify at all)

Steps to Reproduce:

  1. Use Firefox to view that testcase. Click the left/right arrows, and notice how long the animation takes to complete.
  2. Now, visit about:config and set preference layout.frame_rate to 2 (to limit the browser repaint cycle to 2-per-second).
  3. Look at the testcase again and try clicking the arrows.

Actual Results: The animation still plays every single frame of animation, but in extremely slow motion. So it takes ~20 seconds to complete the animation.

Expected Results: Animation should still take the same amount of time (1 second or so), and each frame should simply make a huge amount of progress.

Notes:

dholbert commented 2 months ago

Note that even for users who don't have any particular special configuration, requestAnimationFrame does not guarantee a consistent frame rate, and users presumably will see occasional unexpected hiccups (speedups/slowdowns) with Flickity animations as a result of the fact that Flickity seems to be relying on a fixed frame-rate.

The MDN article on this feature makes several mentions of this:

dholbert commented 2 months ago

Here's a screencast showing this bug in action: screencast of bug.webm

(The bug is in the latter half of the screencast, after I toggle Firefox's layout.frame_rate pref in about:config.)

dholbert commented 2 months ago

It seems that Flickity isn't tracking how much time passes between requestAnimationFrame calls, to ensure a consistent animation duration and animation speed.

Grounding this in the code a bit: the main animation loop seems to be here:

proto.animate = function() {
  this.applyDragForce();
  this.applySelectedAttraction();

  let previousX = this.x;

  this.integratePhysics();
  this.positionSlider();
  this.settle( previousX );
  // animate next frame
  if ( this.isAnimating ) requestAnimationFrame( () => this.animate() );
};

https://github.com/metafizzy/flickity/blob/a64cc33052150417bb84c724b937097308519d6e/js/animate.js#L27-L38

This function and its helpers (e.g. integratePhysics, positionSlider, settle) don't seem to consider the time-elapsed-since-the-last-frame at all. It looks like the position advances by this.velocity, which is a number that decreases/dampens each frame, in a way that's trying to be physics-based but not taking time into account.

ChasBelov commented 2 months ago

For me, in Firefox 126, with layout.frame_rate set to 1, I'm seeing full-speed animation when I click the previous chevron. It takes less than a second to go from rectangle 3 to rectangle 2.

https://github.com/metafizzy/flickity/assets/59780179/d87f8c7c-5778-498a-8a30-bcbde94f2e22

dholbert commented 2 months ago

For me, in Firefox 126, with layout.frame_rate set to 1, I'm seeing full-speed animation when I click the previous chevron

Closing the loop on this: as discussed in https://bugzilla.mozilla.org/show_bug.cgi?id=1894842#c11 and following comments, it seems @ChasBelov's issue was due to another about:config setting (possibly privacy.resistFingerprinting) which was interfering and causing layout.frame_rate to be ignored.

Given that, let's set @ChasBelov's comment aside & keep this github issue focused on the thing discussed in my initial comment which is the fact that Flickity animations take forever if the actual animation-frame-frame rate is low (via e.g. user preferences like layout.frame_rate in cases where the browser does in fact honor that).