Bug Tracker

Modify

Ticket #9381 (closed bug: fixed)

Opened 3 years ago

Last modified 13 months ago

animations halt when the browser is out of focus due to requestAnimationFrame

Reported by: super@… Owned by: timmywil
Priority: low Milestone: 1.6.3
Component: effects Version: 1.6.1
Keywords: needsreview Cc:
Blocking: Blocked by:

Description

Hello,

I found a little nasty bug (:)) when using the fadeIn() and fadeOut() functions with setTimeout() (probably also a problem with setInterval()) and version 1.6.1 (and 1.6) in Chrome.

The problem is that all animations using fadeIn() and fadeOut() seem to be blocked when the browser window is minimized. When it is reopened the animations fires rapidly until all have been executed, and it resumes to work normally.

I have created an example on jsfiddle.  http://jsfiddle.net/9RNzC/

Just open it, minimize the browser window, wait for 10 seconds (etc - it changes every 3 second), and reopen it again, and it will fire the animation effects rapidly, like they have been blocked while minimized.

It works fine with version 1.5.2 so something have probably changed between 1.5.2 and 1.6 in the animate function.

Hope it helps.

Best regards

Martin

Change History

comment:1 Changed 3 years ago by rwaldron

  • Owner set to gnarf
  • Status changed from new to assigned
  • Component changed from unfiled to effects

I believe this is caused by requestAnimationFrame, which stops when the window is out of focus.

assigning to gnarf for assessment

comment:2 Changed 3 years ago by super@…

Just noticed that i forgot to change the out commented code using the show() and hide() functions, when i tried to simplify the code.

I have just updated it, if you want to see that it works when using the show() and hide() functions (remember to out comment the code and comment out the fadeIn() and fadeOut() calls above()).

 http://jsfiddle.net/9RNzC/1/

It doesnt make a difference for the actual problem, just wanted to note it in case you were trying it out and wondered why it isnt working.

comment:3 Changed 3 years ago by gnarf

  • Owner gnarf deleted
  • Status changed from assigned to open

Yeah, sadly because of the way that requestAnimationFrame() works (it does't give you any updates while the page isn't focused) this is exactly what will happen.

  • unfocus tab
  • You queue an animation....
  • No animation frames are given, so you queue another animation, etc, etc
  • focus tab
  • Animation Frame is given, you finally get the second frame of the first animation
  • First animation Completes, second starts
  • The rest of your queue plays out.

To avoid: Don't use setTimeout() like that directly, instead use the callback of one of the animations to setTimeout for the next transition... Or you could use the queue itself...

Either of these solutions will work around the issue:

elem.fadeIn(1000, function() {
    setTimeout( nextCycle, 2000 );
});
// or....
elem.fadeIn(1000).delay(2000).queue(function( next ) {
    // tell the cycler to move on
    nextCycle();
    // then call the next queue function
    next();
});

I don't see any way that we can avoid the "huge queue playback" of an animation like this that doesn't expect "stalling" time when the window isn't focused.

comment:4 Changed 3 years ago by timmywil

#9411 is a duplicate of this ticket.

comment:5 Changed 3 years ago by timmywil

  • Keywords needsdocs added
  • Priority changed from undecided to low

gnarf's suggestion is really what should happen instead. I think we should document this behavior and leave it as is.

comment:6 Changed 3 years ago by gnarf

 http://api.jquery.com/animate/ -- Added the following note to the animation functions:

Because of the nature of requestAnimationFrame(), you should never queue animations using a setInterval or setTimeout loop. In order to preserve CPU resources, browsers that support requestAnimationFrame will not update animations when the window/tab is not displayed. If you continue to queue animations via setInterval or setTimeout while animation is paused, all of the queued animations will begin playing when the window/tab regains focus. To avoid this potential problem, use the callback of your last animation in the loop to set a timeout to start the next animation.

comment:7 Changed 3 years ago by timmywil

  • Keywords needsdocs removed
  • Status changed from open to closed
  • Resolution set to wontfix

comment:8 Changed 3 years ago by Martin

If it is possible for you somehow, it would be great if you could let some of all the major plugin makers know about this, because this will break a whole lot of plugins.

When making my plugin which just cycles between elements and fade them in and out, i looked at other plugins, and as far as i remember all of them will break because of this.

Why not go back to how it was before ? It worked fine, and how you would expect it to work. Now with then new version you have to do some kind of "hack" to make it play nice with jQuery.

Any way, great work with jQuery, it is really great. :)

comment:9 Changed 3 years ago by Adam

Good way to work around this issue is to disable repeating animations using window.onfocus and window.onblur. By doing so animationts are not stacked together when the browser gets minimized or another tab is active. Example:

function windowActive() { focus = true; }
function windowInactive() { focus = false; }
window.onfocus = windowActive;
window.onblur = windowInactive;

var animation = function(){
 if(focus){
  //code
 }
}
Last edited 3 years ago by gnarf (previous) (diff)

comment:10 follow-up: ↓ 16 Changed 3 years ago by anonymous

Adam: That is not a good solution because it will stop the animation even if the user is able to see the window. You could have 2 windows (or more) running side by side but without having focus.

Wasnt it one of the ideas behind jQuery in the first place that you shouldnt have to do "hacks" and write browser specific code to make it work ?

I dont want to mix my transition logic with my "autoplay" (cycle) logic as they are seperate things and dont belong together. The animation logic should also work when not set to automatically cycle between elements, so user can just click the element they want to go to.

The work arounds suggested earlier is not great. I much prefer it working like expected without having to do hacks to make jQuery play nice. It was working fine with older versions, so it should be possible.

comment:11 Changed 3 years ago by anonymous

currently to fix the problem i have just implemented a varaible (ready) containing the state.

So before i do an animation I set it to false, and at the end of the animation (using the callback) i set it to true.

In the autoplay function i just check the ready variable, and only call the animation method if true.

Then the animations doesnt stack up, as the autoplay stops calling when animations start to not get executed.

comment:12 Changed 3 years ago by anonymous

I had similar problem with animate function, and it took me quite a while to repeair the whole slideshow application, I've created, which broke after moving to the latest release of jQuery. It's quite serious bug in my opinion since, most "autocycle" applications will stop to work properly in chrome. I am wondering why it was classified as low priority bug and closed with wontfix flag?!

comment:13 Changed 3 years ago by timmywil

  • Keywords needsreview added
  • Status changed from closed to reopened
  • Resolution wontfix deleted

This has been closed as wontfix because really the choice here is we can nix requestAnimationFrame and go back to setInterval for all browsers and miss out on the performance/smoothness advantages or document that users should use callbacks instead of stacking animations blindly. If this is a problem for enough people, we will probably have to switch back to setInterval or find some glorious hack that fixes this bug without too much code and keeps requestAnimationFrame, but I cannot think of a viable solution.

comment:14 Changed 3 years ago by timmywil

  • Status changed from reopened to open

comment:15 Changed 3 years ago by dmethvin

I think the writing is on the wall; it seems more likely that the other vendors will follow Chrome than for Chrome to revert this behavior. By not doing requestAnimationFrame animations when a user cannot see them, the browser is saving CPU time -- and more importantly, battery life in mobile devices.

timmywil is right that "blindly stacking animations" is a bad idea. For example if you want to show a slide for 10 seconds, have the callback for the completion of showing of the slide set a timeout for 10 seconds or use .queue() to queue the timeout. Don't have some unrelated code just blindly set a 10-second timeout and force a transition regardless. If the user minimized your slide show, they HAVE NOT SEEN THE SLIDES.

comment:16 in reply to: ↑ 10 Changed 3 years ago by gnarf

Replying to anonymous:

Adam: That is not a good solution because it will stop the animation even if the user is able to see the window. You could have 2 windows (or more) running side by side but without having focus.

I agree, this is the wrong way to solve the problem.

Wasnt it one of the ideas behind jQuery in the first place that you shouldnt have to do "hacks" and write browser specific code to make it work ?

I'm not sure how any of this constitutes a need to write a "hack" to support a browser. You are instead updating your code to support new versions of jQuery and the improved handling of animation timers that will save your laptops battery life when you tab out of that cycling page...

I dont want to mix my transition logic with my "autoplay" (cycle) logic as they are seperate things and dont belong together. The animation logic should also work when not set to automatically cycle between elements, so user can just click the element they want to go to.

So, I fail to see how this is a problem... Make your "animation" function accept a callback that gets passed along to the actual .animate() - problem solved. The situations where you don't care about getting a callback are still fine, because your "complete" callback will be undefined:

function animateElementIn( in, done ) {
   // assuming current is already set somewhere...
   current.fadeOut( 1000 );
   in.fadeIn( 1000, done );
}

function loopyCycler() {
    animateElementIn( next, function() {
        setTimeout( loopyCycler, 2000 );
    });
}

The work arounds suggested earlier is not great.

Explain any issues with my earlier examples, and I might take the time to write a better work around for your specific needs. Oh, and personally, I think using setTimeout or setInterval to queue "cycles" is "not great" you should have been using the .animate() callback or .queue() in the first place...

Last edited 3 years ago by gnarf (previous) (diff)

comment:17 Changed 3 years ago by anonymous

In some applications the problem is really serious. Using a callback function as a workaround is nice for a simple predefined things, but that is not always possible.

For example, if you have a game application where there are a lot of timeouts and intervals based on previous user input and game state. Using animations instead of timers? That's crazy.

A nice solution is to drop animations at all in inactive state.
But how to easily define if the state is inactive and animation won't play?
I suggest adding some property/function to determine that. To make it possible things like

if(IsAnimationsRunning) $(something).animate(...); else $(something).clearQueue();

comment:18 Changed 3 years ago by timmywil

  • Summary changed from setTimeout and fadeIn/fadeOut blocking in Chrome to animations halt when the browser is out of focus due to requestAnimationFrame

comment:19 Changed 3 years ago by timmywil

#9711 is a duplicate of this ticket.

comment:20 Changed 3 years ago by timmywil

#9825 is a duplicate of this ticket.

comment:21 Changed 3 years ago by rwaldron

This is an additional approach to managing things that might happen when the window has lost focus:

 http://jsfiddle.net/rwaldron/KK5mP/

based on a request in -dev

comment:22 Changed 3 years ago by gnarf

 https://github.com/jquery/jquery/pull/436 - Pull request to implement a way to disable rAF and revert to setInterval always.

comment:23 Changed 3 years ago by gnarf

 https://github.com/gnarf37/jquery/compare/jquery:master...ticket_9381_2 - Here is yet another possible solution to the problem inspired by a jaubourg comment in -dev.

comment:24 Changed 3 years ago by rwaldron

#9865 is a duplicate of this ticket.

comment:25 Changed 3 years ago by anonymous

I not sure, just a jquery n00b but I had this problem in google chrome when opening another tab. When returning to that tab the animations ran wild. Just stumbled on to this tip:  http://www.learningjquery.com/2009/01/quick-tip-prevent-animation-queue-buildup

seems simply adding the .stop() solves all problems, at least for me it did, in two completly different animation scripts even. Just my 2 cents.

comment:26 Changed 3 years ago by lrbabe

I'd like to give this bug a try as well. I know of a better solution to prevent this behavior and would like to see if it can be applied here.

louisremi

comment:27 Changed 3 years ago by lrbabe

PR is here:  https://github.com/jquery/jquery/pull/446

Animation frameworks have to deal with the problems that occur when requestAnimationFrame powers the animation loop and the tab loses focus. The general idea is to artificially limit the interval between two animation ticks. This was illustrated in Seth Ladd's "Intro to HTML5 Game Development" during Google I/O 2011:  http://io-2011-html5-games-hr.appspot.com/#36 He calculates the time delta between two animation ticks and limit this value to 60ms

var dT = Math.min( now - prevNow, 60 );

The way jQuery animations work is more complex than Seth's ones, as animations aren't based on the time delta between the previous tick and the current one, but between the beginning of the animation (startTime) and the current one. The only way to limit the interval between two ticks is to move the value of startTime forward in time.

With this 5loc patch, jQuery detects when the interval takes longer than usual and moves startTime accordingly to effectively pause the animation. Once the tab is focused again, the animation continues where it stopped.

comment:28 Changed 3 years ago by timmywil

#9957 is a duplicate of this ticket.

comment:29 Changed 3 years ago by timmywil

We've discussed the raf issue in the monday meeting and have decided to remove raf until it is both fleshed out further by all of the browser vendors and we have reliable solutions to the problems that come with supporting a requestAnimationFrame animate. This does not rule out the possibility of it's eventual permanent inclusion into jQuery's animate in either 1.7, or possibly 1.8, but we unknowingly opened up a can of worms by adding this too soon and we need to clear the detritus.

comment:30 Changed 3 years ago by timmywil

  • Owner set to timmywil
  • Status changed from open to assigned

comment:31 Changed 3 years ago by timmywil

  • Milestone changed from 1.next to 1.6.3

comment:32 Changed 3 years ago by timmywil

  • Status changed from assigned to closed
  • Resolution set to fixed

Remove requestAnimationFrame support. Fixes #9381.

Changeset: 2053d1c621e8ef65b79d6b339d7336c732ed1b82

comment:33 Changed 3 years ago by dmethvin

#10281 is a duplicate of this ticket.

comment:34 Changed 13 months ago by mathias

Could this be a solution?

Using a plugin like  https://github.com/mathiasbynens/jquery-visibility you could disable any animations while the window/tab isn’t focused, and re-enable them as soon as the window gains focus again. (The plugin uses the Page Visibility API and falls back to good old focus and blur in older browsers.)

comment:35 Changed 13 months ago by dmethvin

Let's not tag new discussions onto long-closed tickets.

Please follow the  bug reporting guidlines and use  jsFiddle when providing test cases and demonstrations instead of pasting the code in the ticket.

View

Add a comment

Modify Ticket

Action
as closed
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.