Bug Tracker

Opened 12 years ago

Closed 12 years ago

Last modified 11 years ago

#9381 closed bug (fixed)

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

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

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 (35)

comment:1 Changed 12 years ago by Rick Waldron

Component: unfiledeffects
Owner: set to gnarf
Status: newassigned

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

assigning to gnarf for assessment

comment:2 Changed 12 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 12 years ago by gnarf

Owner: gnarf deleted
Status: assignedopen

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 12 years ago by Timmy Willison

#9411 is a duplicate of this ticket.

comment:5 Changed 12 years ago by Timmy Willison

Keywords: needsdocs added
Priority: undecidedlow

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

comment:6 Changed 12 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 12 years ago by Timmy Willison

Keywords: needsdocs removed
Resolution: wontfix
Status: openclosed

comment:8 Changed 12 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 12 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 12 years ago by gnarf (previous) (diff)

comment:10 Changed 12 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 12 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 12 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 12 years ago by Timmy Willison

Keywords: needsreview added
Resolution: wontfix
Status: closedreopened

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 12 years ago by Timmy Willison

Status: reopenedopen

comment:15 Changed 12 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 12 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 12 years ago by gnarf (previous) (diff)

comment:17 Changed 12 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 12 years ago by Timmy Willison

Summary: setTimeout and fadeIn/fadeOut blocking in Chromeanimations halt when the browser is out of focus due to requestAnimationFrame

comment:19 Changed 12 years ago by Timmy Willison

#9711 is a duplicate of this ticket.

comment:20 Changed 12 years ago by Timmy Willison

#9825 is a duplicate of this ticket.

comment:21 Changed 12 years ago by Rick Waldron

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 12 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 12 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 12 years ago by Rick Waldron

#9865 is a duplicate of this ticket.

comment:25 Changed 12 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 12 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 12 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 12 years ago by Timmy Willison

#9957 is a duplicate of this ticket.

comment:29 Changed 12 years ago by Timmy Willison

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 12 years ago by Timmy Willison

Owner: set to Timmy Willison
Status: openassigned

comment:31 Changed 12 years ago by Timmy Willison

Milestone: 1.next1.6.3

comment:32 Changed 12 years ago by Timmy Willison

Resolution: fixed
Status: assignedclosed

Remove requestAnimationFrame support. Fixes #9381.

Changeset: 2053d1c621e8ef65b79d6b339d7336c732ed1b82

comment:33 Changed 12 years ago by dmethvin

#10281 is a duplicate of this ticket.

comment:34 Changed 11 years 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 11 years ago by dmethvin

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

Note: See TracTickets for help on using tickets.