Jump to content
Search Community

Animations pause when browser tab is not visible

BowserKingKoopa test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

I'm very new to gsap and I've run into this problem. I'm using gsap to animate a progress bar, but when the browser tab or window is not visible the progress bar animation pauses. When I switch back to that particular tab it resumes. I need the animation to keep running even when the tab is not visible. How can I achieve this in gsap?

 

Here's my code:

 

var progressBarTimeline = new TimelineLite();

progressBarTimeline.fromTo(progressBarElement, 10, {width:0}, {width:100, ease:Linear.easeNone});

Link to comment
Share on other sites

Hello BowserKingKoopa, and Welcome to the GreenSock Forums!

 

Do you have an example showing this behavior: If not can you please provide a codepen example so we can better help you by seeing your code live and editable.

 

Here is a nice video tut by GreenSock on How to make a GreenSock codepen demo!

 

Without seeing your code in action, you can try and pause() the timeline when your active tab loses focus and then when you return resume() the timeline again like you never left ;)

 

Here is an example that checks for window focus / blur events. It also checks for browser tab focus and blur using HTML5 Visibility API .. so you can pause() and resume() the animation on window tab focus / blur:

 

Example of usage:

See the Pen sxgJl by jonathan (@jonathan) on CodePen

 

To check if the current active tab has focus or not, using the HTML5 Visibility API.

// main visibility API function 
// use visibility API to check if current tab is active or not
var vis = (function(){
    var stateKey, 
        eventKey, 
        keys = {
                hidden: "visibilitychange",
                webkitHidden: "webkitvisibilitychange",
                mozHidden: "mozvisibilitychange",
                msHidden: "msvisibilitychange"
    };
    for (stateKey in keys) {
        if (stateKey in document) {
            eventKey = keys[stateKey];
            break;
        }
    }
    return function(c) {
        if (c) document.addEventListener(eventKey, c);
        return !document[stateKey];
    }
})();

Usage of the HTML5 Visibility API like this:

// check if current tab is active or not
vis(function(){
					
    if(vis()){	
        	
	setTimeout(function(){  
            // tween resume() code goes here          
            console.log("tab is visible - has focus");
        },300);		
												
    } else {
	
        // tween pause() code goes here
        console.log("tab is invisible - has blur");		 
    }
});

You will still need the following to check if other windows have focus or not (blur). Chromium type browser like Google Chrome or Latest Opera do not fire all the time when binding the event with jQuery window, so you need to check for window.addEventListener.

// check if browser window has focus		
var notIE = (document.documentMode === undefined),
    isChromium = window.chrome;
      
if (notIE && !isChromium) {

    // checks for Firefox and other  NON IE Chrome versions
    $(window).on("focusin", function () { 

        // tween resume() code goes here
        setTimeout(function(){            
            console.log("focus");
        },300);

    }).on("focusout", function () {

        // tween pause() code goes here
        console.log("blur");

    });

} else {
    
    // checks for IE and Chromium versions
    if (window.addEventListener) {

        // bind focus event
        window.addEventListener("focus", function (event) {

            // tween resume() code goes here
            setTimeout(function(){                 
                 console.log("focus");
            },300);

        }, false);

        // bind blur event
        window.addEventListener("blur", function (event) {

            // tween pause() code goes here
             console.log("blur");

        }, false);

    } else {

        // bind focus event
        window.attachEvent("focus", function (event) {

            // tween resume() code goes here
            setTimeout(function(){                 
                 console.log("focus");
            },300);

        });

        // bind focus event
        window.attachEvent("blur", function (event) {

            // tween pause() code goes here
            console.log("blur");

        });
    }
}

You will also notice that i have a setTimeout() in the focus event handler so the tab/window has enough time to gain focus, and so the focus event handler fire consistently. I noticed Firefox and Google Chrome were not resuming correctly unless i added the setTimeout().

 

The reason i use the HTML5 Visibility API is because some browsers like Chrome wont trigger the tab blur unless you actually click inside the other  new tab, simply scrolling with the mouse wont trigger the event.

 

Also according to the HTML5 Visibility API. But please take note, the browser won't fire the visibilitychange event when the browser window is minimized or hidden is true.

Does that help?

  • Like 1
Link to comment
Share on other sites

There are two things that contribute to this behavior:

  1. By default, GSAP uses the requestAnimationFrame API to drive its "ticks". This is widely considered the best way to do it because it delivers the best performance and synchronizes updates with screen refreshes/repaints. In most browsers, requestAnimationFrame fires about 60 times per second EXCEPT on inactive tabs which typically run at around 2fps or less. This is a feature, not a bug - the browser vendors are essentially "powering down" those inactive tabs in order to be more efficient and conserve battery power on mobile devices. They figure "why should we be updating the screen 60 times per second when the user can't even see it? It's a waste of CPU/GPU cycles". 
  2. GSAP has a relatively new feature that's automatically enabled called lagSmoothing(). Read about it at https://www.greensock.com/gsap-1-12-0-performance/. Basically, whenever there's more than 500ms between "ticks", it will adjust the core timing mechanism to prevent things from jumping ahead. 

So in your case, when you switch tabs, the browser is dropping the FPS down to 2 (or less, depending on the browser) and then when you activate the tab again, lagSmoothing() says "hey, there has been a lot of time between ticks, so let's adjust the core time to smooth things out and prevent a big jump". 

 

There are two things you can do:

  1. Force the engine to NOT use requestAnimationFrame (in which case it will use setTimout() under the hood) like this: TweenLite.ticker.useRAF(false). This means it won't "power down" on inactive tweens, but it also means that your site will eat up more battery in those cases and overall animation performance may degrade slightly (probably not noticeable). 
  2. Turn off lagSmoothing() like this: TweenLite.lagSmoothing(0). The down side is that you won't get the benefit of the engine automatically sensing lag and correcting for it, but frankly in most cases this isn't a big issue. If you see jumps in your animation, though, it's wise to have lagSmoothing() enabled. 

A fancy solution would be to write your own script so that while the tab is active, you have lagSmoothing() enabled and the ticker uses requestAnimationFrame, but then when the user switches tabs, you turn off lagSmoothing() (and if that doesn't work well enough for you, try making the ticker not use requestAnimationFrame). 

 

I hope that helps. 

 

Again, none of this behavior is a "bug" - these are very valuable features in fact :) 

  • Like 3
Link to comment
Share on other sites

I turned off lagSmoothing and it fixed my problem. This behavior still seems like a bug to me. Smoothing out lag is a great feature, but a minimized (or otherwise invisible) window is not lag. Having an animation pause when the window is invisible is, I imagine, rarely what is expected or desired.

  • Like 1
Link to comment
Share on other sites

I guess it all depends on what your desired behavior is. In most of my projects, I'd much rather have animation pause when I switch tabs, and pick right up where it left off when I return. That way, I didn't miss anything. The same goes for when someone finger-scrolls on an iPad - Apple forces all animation to stop during the scroll, and some people would argue that GSAP should prioritize accurate timing (thus skip forward when the user releases their touch), but others would say it's much nicer if the animation just continues smoothly from where it was paused. Is there one "right" way to do it? Not really. It's subjective. But we tried to build GSAP in a super flexible way so that you can get the behavior that YOU want. 

 

Glad it fixed things for you. 

 

By the way, did you try changing the lag threshold to something like 700 instead of 500? I wonder if that'd be enough to not trigger lagSmoothing while a tab is inactive. 

  • Like 2
Link to comment
Share on other sites

Is there a way to specify lagSmoothing on a per tween basis? It seems like one global setting isn't flexible enough.  

 

I'm building a game.  Most animations correspond to 'real' things happening in the game world therefore accurate timing is important.  They can't ever 'pause' when someone minimizes the window or lags a little bit.  However other animations, more eye-candy in nature, might benefit from the lagSmoothing feature.

  • Like 1
Link to comment
Share on other sites

And for the record, the way lagSmoothing() was engineered at the global level makes it extremely efficient. If we were to switch to a per-tween setting, it would be exponentially more complex and CPU-heavy, not to mention code-heavy. Plus that would mean that some tweens could get out of sync with others and that could be a big problem of its own. Trust me - it's a good thing that lagSmoothing() is a global setting, although I totally see what you're saying about a use case where it could be handy on a per-tween basis ;)

  • Like 1
Link to comment
Share on other sites

  • 1 year later...

Jonathan,

 

great script for playing / pausing the timeline when the browser has no focus. I think this would help mobile devices to save more battery power.

 

I tried using it for HTML5 (mobile) banners. The script works well on desktop browsers. But the script seems to pause the tweening at the beginning (Not beginning to play at all) when i am using Safari Mobile on my iPhone5S. Perhaps i should not use it for mobile HTML5 Banner? What is with other smartphones / tablets / operating systems and mobile browsers?

  • Like 1
Link to comment
Share on other sites

Hello Technics1210, and welcome to the GreenSock forum! Technics are the best!

 

Even when you click the tab to change focus.. the browser sometimes will not take full focus unless you click inside the viewport so it knows it has taken focus. Also according to the HTML5 Visibility API. It won't fire the visibilitychange event when the browser window is minimized or hidden is true.

 

Also if you have an example demo we can see your GSAP code to see if you might have any callbacks  in your timeline. SInce the GSAP pause() method has suppressEvents  parameter defaults to true.

 

http://greensock.com/docs/#/HTML5/GSAP/TimelineMax/pause/

 

Pause - below taken from GSAP docs

 

Pauses the instance, optionally jumping to a specific time.
 

If you define a time to jump to (the first parameter, which could also be a label for TimelineLite or TimelineMax instances), the playhead moves there immediately and if there are any events/callbacks inbetween where the playhead was and the new time, they will not be triggered because by default suppressEvents (the 2nd parameter) is true. Think of it like picking the needle up on a record player and moving it to a new position before placing it back on the record. If, however, you do not want the events/callbacks suppressed during that initial move, simply set the suppressEvents parameter to false.

//pauses wherever the playhead currently is:
myAnimation.pause();
 
//jumps to exactly 2-seconds into the animation and then pauses:
myAnimation.pause(2);
 
//jumps to exactly 2-seconds into the animation and pauses but doesn't suppress events during the initial move:
myAnimation.pause(2, false);

Also Apple has a developer docs for new window and tabs api: https://developer.apple.com/library/safari/documentation/Tools/Conceptual/SafariExtensionGuide/WorkingwithWindowsandTabs/WorkingwithWindowsandTabs.html

 

Here is a great codepen by GreenSock on how to create a codepen demo example. This way we can see your code a live editable environment!

 

Resources:

GSAP pause() : http://greensock.com/docs/#/HTML5/GSAP/TimelineMax/pause/

GSAP resume() : http://greensock.com/docs/#/HTML5/GSAP/TimelineMax/resume/

GSAP play() : http://greensock.com/docs/#/HTML5/GSAP/TimelineMax/play/

HTML5 Visibility API : https://developer.mozilla.org/en-US/docs/Web/Events/visibilitychange

 

 

:)

  • Like 1
Link to comment
Share on other sites

  • 3 months later...

HI. 

Thanks. I found this thread and it fixed my problem with my image slider.

I was animating elements within a cycle2 slider that get triggered on slide change using the cycle2 api. 

All the animations got stuck and stopped half way though when coming back to the tab after some time.

               
TweenLite.ticker.useRAF(false);

TweenMax.lagSmoothing(0);

 

using the above fixed the problem for me.

I expect this is because cycle2 uses set timeout to trigger the slide change and this is what my gsap animations are being fired by. When i change tab the timeouts are being triggered but the gsap animations had not finished because they are being slowed down by the way browsers slow down requestAnimationFrame when the tab is not active.

  • Like 1
Link to comment
Share on other sites

  • 1 year later...

Hi, I have also similar problems with animation, but my sample is more difficult:

 

See the Pen xgZMVd by anon (@anon) on CodePen

 

So, as I researched, I have next situation:

1. TweenMax (as it uses requestAnimationFrame) freezes when tab is inactive.

2. setInterval keep going when tab is inactive (also it's delay may change when tab is inactive, depends on browser)

3. Is there any other javascript functionality that changes when tab is inactive?

 

Then I have 2 solutions:

1. Freeze whole game, when tab is inactive.

2. Keep going, when tab is inactive.

 

With first solution I have next problem: as TweenMax uses requestAnimationFrame, it works correct according to this solution (freezes animation), but how can I freeze intervals and timeouts when tab is inactive and then resume intervals and timeouts?

 

With second solution I can use TweenMax.lagSmoothing(0) and TweenMax.ticker.useRAF(false) for animation and it works, but anyway something goes wrong with intervals and/or timeouts. I expected that animation goes wrong because of change of interval delay to 1000+ ms when tab is inactive (according to http://stackoverflow.com/questions/15871942/how-do-browsers-pause-change-javascript-when-tab-or-window-is-not-active), but I disabled acceleration and set delays to 2000ms and it didn't help.

 

Please help me with at least one solution. Better with both to have some variety.

 

By the way, TweenMax is great framework, thank you very much for its development and support.

Link to comment
Share on other sites

Yep, pretty much all browsers these days will kinda "power down" when the tab is inactive (to save battery/resources). 

 

It sounds like the crux of your question boils down to wanting to ensure that GSAP and your setTimeout() and setInterval() calls don't get out-of-sync. Right?

 

I'd strongly recommend avoiding setTimeout() and setInterval() in your code. Instead, I'd just use GSAP for ALL of that stuff. Remember, there's a TweenLite.delayedCall() that basically does the same thing as setTimeout(), but it would be perfectly synchronized with GSAP. Or if it's helpful, you could TweenLite.ticker.addEventListener("tick", yourFunction) to call yourFunction every time the engine updates. 

 

Does that help?

  • Like 2
Link to comment
Share on other sites

It sounds like the crux of your question boils down to wanting to ensure that GSAP and your setTimeout() and setInterval() calls don't get out-of-sync. Right?

Yes, that's what I'm trying to do. Stable animation without chaotic changes.

 

Remember, there's a TweenLite.delayedCall() that basically does the same thing as setTimeout(), but it would be perfectly synchronized with GSAP.

Yeah, that helped, animation is synced during the game even if I switch tabs, it's great! But when I use recursive delayedCall as setInterval, I have little problem: using delayedCall is not accurate enough. I rarely have problem when delayedCall with 1s delay is invoked faster then 5th recursive invocation of delayedCall with 199.4ms delay (summary 997ms). Of course it's not a problem of delayedCall, it's just because it tooks some time to run code before next delayedCall invocation. Now I'm trying to rearchitect calculation logic according to delayedCall feature. But it would be easier if TweenMax had direct functionality for setInterval or at least functionality to cancel upcoming delayedCall like clearTimeout/clearInterval.
 
Also I found some bug with creating new TweenMax object. I thought that this is a sync problem too, but I found that this is another problem. Rarely and randomly when running this code:
    animateStick: function ($stick) {
        var _this = this;
        var translateYValue = this.windowHeight + -this.stickTop;


        var tween = new TweenMax($stick, this.stickDuration, {
            y: translateYValue, ease: Power0.easeNone, onComplete: function () {
                $stick.remove();
                _this.stickTweensArray.shift();
            }
        });
        console.log(tween);
        _this.stickTweensArray.push({tween:tween, $stick:$stick});
    }

wrong tween creates with next properties:

  • _active: false (when correct tweens have true);
  • _dirty: true (when correct tweens have false);
  • _propLookup: Array[0] (when correct tweens have Array[1] with y:Object inside);
  • _startTime: NaN;
  • matrix(1, 0, 0, 1, 0, 0) (when correct tweens have matrix(1, 0, 0, 1, 0, y(t));
  • also this wrong tween has less properties than the correct one.
I thought may be my code is wrong and object $stick is wrong, but I checked it in debug and everything is fine with $stick. The bug is reproduced more often when the game goes faster. This bug is critical enough for me, so I hope you can help me with that.
 
PS. You can observe this bug even in sample that i posted before, but very rarely (mb once or twice per run) and only almost at top speed (after 40s of running)

See the Pen xgZMVd by anon (@anon) on CodePen

Link to comment
Share on other sites

Hm, that doesn't sound like a GSAP bug (though I could be wrong). It sounds more like a logic flow issue in the code. Would you mind creating a reduced test case that reliably reproduces the issue? You've got 150 lines of code currently - can you do it in less than 50? I have found that when I force myself to do that, I almost always stumble across a solution. I strip things down to the absolute basics, and then slowly add complexity until it breaks and then I know exactly what step makes it break. 

 

Also, I noticed that you're directly editing private properties on the tweens, like _startTime and _duration. In GSAP, anything that's prefixed with "_" is **not** supposed to be edited by users. This could very well be the source of the problem. If you want to get/set those values, please use the proper methods like duration() and startTime(). 

 

Oh, and if you're looking for the clearTimeout()-like function for delayedCall(), you should be able to simply do TweenLite.killDelayedCallsTo(yourFunction) or TweenLite.killTweensOf(yourFunction) (either one works - they both do the same thing). Or you can maintain a reference to the tween that's returned and kill that, like:

var myCall = TweenLite.delayedCall(1, myFunc);
//then later....
myCall.kill();
//or
TweenLite.killDelayedCallsTo(myFunc);
  • Like 1
Link to comment
Share on other sites

So, I tried different scenarios: firstly I removed acceleration of stick generation period and made it constant (1s) - the bug did not reproduce:

See the Pen oBLGqe by anon (@anon) on CodePen

Then i decided that problem is in acceleration of stick generation period, so I added it and removed acceleration of existing sticks (changing duration and startTime of elements) - the bug did not reproduce:

See the Pen OWREzv by anon (@anon) on CodePen

So the problem is reproduced only when I have combination of these functionalities:

See the Pen xgZMVd by anon (@anon) on CodePen

 

I can't get why it happens. I could understand if it would be because of properties change, it could affect somehow TweenMax functionality. But it can't be reproduced when I ONLY change the properties. On other hand I have functionality where I only decrease delay of creating next element - doesn't affect TweenMax in any side. And only combination of these two functionalities causes bug to be reproduced.

That's why I think it might be GSAP bug, I can't get how my code can rarely create tween with isActive = false  (by the way when tween is just created it has _active false and isActve() true and only after few moments tween's isActive() becomes false, that's why I created 

  var length = stickTweensArray.length;
  if (length >= 2 && !stickTweensArray[length - 2].tween.isActive()) {
    alert ("isActive() = false");
  }

on previously created tween).

 

I also refactored code to be more clear and accurate and removed using private properties and it didn't help.

Link to comment
Share on other sites

Also I tried to change changingSpeedFunction from

function changingSpeedFunction(x){
    var y = Math.pow(2, (x / 20));
    return y;
}

to

function changingSpeedFunction(x){
    var y = 1;
    return y;
}

another code stays the same and bug didn't reproduce.

But when I changed to (after 1 second speed and stick creation became twice faster and then it's same for rest of time)

function changingSpeedFunction(x){
    var y = 2;
    return y;
}

Bug is reproduced. So seems like problem appears when tween properties changed and stick creation became faster. But that doesn't give information how to fix it.

Link to comment
Share on other sites

  • 4 weeks later...

Hey guys, we actually found what was the problem with disappearing objects.

See the Pen xgZMVd by anon (@anon) on CodePen

 

If changeCurrentSticksSpeed() function is triggered just after stick with tween was created (at example after 0.001 seconds), I see that stick already has been moved for 0.1 px (at example), so tween already started his work, but tween.time() shows me 0, when it must be 0.001, that's why other calculations become incorrect and as result i set duration to infinite (because of division by zero) and stick doesn't move. We made a workaround for our case (calculating time as distance to speed division instead of getting it from tween.time()).

Just wanted to notify you about this situation with tween.time(). And it would be great if tween.time() shows correct value even at extremely low values at the beginning.

Link to comment
Share on other sites

I described the issue few posts earlier and you can see it here:

See the Pen xgZMVd by anon (@anon) on CodePen

 

Situation: every period of time object is created. Initial period is 1 second, every second this period decreases due to the function "changingSpeedFunction". Every object just after creation gets tween to move from top to bottom of the screen with constant speed. Every second this speed increases due to the same function "changingSpeedFunction" (duration of tween is decreased, startTime changed to the later one).

So at example object "1" is created at 0 seconds and has tween duration 7 seconds.

At first second object "1" changes duration to 6.8 and startTime changes to 0.1 (all calculations are in changeCurrentSticksSpeed function) and object "2" is created with duration 6.8.

At 1.9 seconds object "3" is created with 6.8 duration.

At 2 seconds all 3 objects are changing duration to 6.6 and startTime to proper one.

And so on.

 

So if I have the situation when object "32" is just created, at example at 22.999983218947 second, tween is also created and tween duration is 5.239. At 23 second changeCurrentSticksSpeed function is triggered. It goes through every existing object and changes their duration and startTime to emulate acceleration. Object "32" is getting affected to. To change his duration and startTime properly I need to have proper tween.time() value: if it's 23 second and tween was created and began movement at 22.999983218947 second then it must has 0.000016781... value, but it has 0. So I have the situation when object already has been moved for may be 0.02 px (position().top) but tween.time() is 0 then i'm trying to get old speed value "var oldSpeed = distance / oldTime" and getting "Infinite" and next evaluations became incorrect.

 

I want to be clear, that I don't need the solution, I already made a workaround (getting tween time from evaluations instead of tween.time() function). Just wanted to notify you about that situation that tween.time() is incorrect at extremely low values when it just began his work.

 

If you want I can make sample at codepen to show alert when tween.time() is 0, tween.startTime() is n and current time is n+m, while m is not 0.

Link to comment
Share on other sites

I saw your previous posts and examples. They just didn't make sense to me.

 

And I wouldn't trust a really low value in JavaScript to begin with due to floating point precision.

http://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript

 

That's why I brought up that thread. You wouldn't have to do all those calculations if you used the ticker as everything uses the same time value. That will also eliminate any potential floating point problems as the time value should be greater than 16ms.

 

Check out this demo of that particle emitter. I lowered the fps down to 15, making run really slow, yet it's still able to accurately fill in all those missing gaps as if it never skipped a beat. And that's with 2,000 different animations running at the same time.

See the Pen ?editors=0010 by osublake (@osublake) on CodePen

 

 

.

  • Like 1
Link to comment
Share on other sites

  • 1 year later...

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...