Skip to main content

Bug Tracker

Side navigation

#9381 closed bug (fixed)

Opened May 21, 2011 11:10PM UTC

Closed August 16, 2011 03:22PM UTC

Last modified March 10, 2013 10:51PM UTC

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

Reported by: super@lynhurtig.dk Owned by: timmywil
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

Attachments (0)
Change History (35)

Changed May 21, 2011 11:29PM UTC by rwaldron comment:1

component: unfiledeffects
owner: → gnarf
status: newassigned

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

assigning to gnarf for assessment

Changed May 22, 2011 12:04AM UTC by super@lynhurtig.dk comment:2

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.

Changed May 24, 2011 06:47AM UTC by gnarf comment:3

owner: gnarf
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.

Changed May 24, 2011 04:22PM UTC by timmywil comment:4

#9411 is a duplicate of this ticket.

Changed May 24, 2011 04:25PM UTC by timmywil comment:5

keywords: → needsdocs
priority: undecidedlow

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

Changed May 24, 2011 05:31PM UTC by gnarf comment:6

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.

Changed May 24, 2011 05:54PM UTC by timmywil comment:7

keywords: needsdocs
resolution: → wontfix
status: openclosed

Changed May 24, 2011 11:00PM UTC by Martin comment:8

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. :)

Changed May 25, 2011 11:09AM UTC by gnarf comment:9

_comment0: 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 \ } \ } \ 1306445589124644

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

Changed May 25, 2011 03:29PM UTC by anonymous comment:10

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.

Changed May 25, 2011 04:04PM UTC by anonymous comment:11

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.

Changed May 26, 2011 01:32PM UTC by anonymous comment:12

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?!

Changed May 26, 2011 02:32PM UTC by timmywil comment:13

keywords: → needsreview
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.

Changed May 26, 2011 02:32PM UTC by timmywil comment:14

status: reopenedopen

Changed May 26, 2011 09:33PM UTC by dmethvin comment:15

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.

Changed May 26, 2011 09:40PM UTC by gnarf comment:16

_comment0: Replying to [comment:10 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"...1306446215484579

Replying to [comment:10 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...

Changed June 29, 2011 06:13AM UTC by anonymous comment:17

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

Changed June 30, 2011 01:28PM UTC by timmywil comment:18

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

Changed June 30, 2011 01:30PM UTC by timmywil comment:19

#9711 is a duplicate of this ticket.

Changed July 14, 2011 02:40PM UTC by timmywil comment:20

#9825 is a duplicate of this ticket.

Changed July 15, 2011 11:12PM UTC by rwaldron comment:21

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

Changed July 16, 2011 09:15PM UTC by gnarf comment:22

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

Changed July 18, 2011 02:07PM UTC by gnarf comment:23

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.

Changed July 20, 2011 12:26AM UTC by rwaldron comment:24

#9865 is a duplicate of this ticket.

Changed July 22, 2011 03:28PM UTC by anonymous comment:25

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.

Changed July 25, 2011 04:54PM UTC by lrbabe comment:26

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

Changed July 26, 2011 02:04PM UTC by lrbabe comment:27

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.

Changed August 02, 2011 01:59PM UTC by timmywil comment:28

#9957 is a duplicate of this ticket.

Changed August 15, 2011 04:27PM UTC by timmywil comment:29

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.

Changed August 15, 2011 04:30PM UTC by timmywil comment:30

owner: → timmywil
status: openassigned

Changed August 15, 2011 04:30PM UTC by timmywil comment:31

milestone: 1.next1.6.3

Changed August 16, 2011 03:22PM UTC by timmywil comment:32

resolution: → fixed
status: assignedclosed

Remove requestAnimationFrame support. Fixes #9381.

Changeset: 2053d1c621e8ef65b79d6b339d7336c732ed1b82

Changed September 14, 2011 11:34PM UTC by dmethvin comment:33

#10281 is a duplicate of this ticket.

Changed March 10, 2013 09:47AM UTC by mathias comment:34

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

Changed March 10, 2013 10:51PM UTC by dmethvin comment:35

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