Pause/resume CSS animations when switching tabs

  • A+
Category:Languages

I have a bunch of long-running CSS animations on a page. I want to pause them when a user switches to another tab and resume them when the user is back to the original tab again. For the sake of simplicity I don't aim for a cross-browser solution at this point; making it work in Chrome should be enough.

document.addEventListener("visibilitychange", function() {   if (document.hidden) {     document.querySelector("#test").classList.add("paused");   } else {     document.querySelector("#test").classList.remove("paused");       } });
div#test {   width: 50px;   height: 50px;   background-color: red;   position: absolute;   left: 10vw;   animation-name: move;   animation-duration: 5s;   animation-fill-mode: forwards;   animation-timing-function: linear; }  @keyframes move {   to {     left: 90vw   } }  .paused {   animation-play-state: paused !important;   -webkit-animation-play-state: paused !important;   -moz-animation-play-state: paused !important; }
<div id="test"></div>

The code above moves a red rectangle horizontally. It takes 5 seconds for the rectangle to complete the animation.

The problem: after the animation starts, switch to another browser tab; after some period of time (longer than 5 seconds) switch back to the first tab.

Expected result: the rectangle continues its path from the point where it left off.

Actual result: most of the times the rectangle appears in its final destination and stops. Sometimes it works as expected. The video demonstrates the problem.

I played with different values for animation-fill-mode and animation-timing-function, but the result was always the same. As rv7 pointed out, sharing the examples in CodePen, JSFiddle, JSBin and stackoverflow JS tool affects the results, so it's better to test directly against a static page on a local HTTP server (or using links below).

For convenience I've deployed the code above to Heroku. The app is a static nodejs HTTP server, which runs on a free subscription, so it may take up to 5 minutes for the page to load for the first time. Testing results against this page:

FAIL Chrome 64.0.3282.167 (Official Build) (64-bit) Linux Debian Stretch (PC)
FAIL Chrome 70.0.3538.67 (Official Build) (64-bit) Windows 10 (PC)
FAIL Chrome 70.0.3538.77 (Official Build) (64-bit) OSX 10.13.5 (Mac mini)
FAIL FF Quantum 60.2.2esr (64-bit) Linux Debian Stretch (PC)
OK Safari 11.1.1 (13605.2.8) (Mac mini)

The page visibility API can be replaced with window focus and blur events like this:

window.addEventListener("focus", function() {     document.querySelector("#test").classList.remove("paused");         });  window.addEventListener("blur", function() {     document.querySelector("#test").classList.add("paused"); }); 

This replacement isn't equivalent however. If a page contains IFRAMEs, interacting with their contents will trigger focus and blur events on the main window. Executing any tab switching code in this case is not correct. This might still be an option in some cases, so I deployed a page for testing here. The results are slightly better:

FAIL Chrome 64.0.3282.167 (Official Build) (64-bit) Linux Debian Stretch (PC)
FAIL Chrome 70.0.3538.67 (Official Build) (64-bit) Windows 10 (PC)
FAIL Chrome 70.0.3538.77 (Official Build) (64-bit) OSX 10.13.5 (Mac mini)
OK FF Quantum 60.2.2esr (64-bit) Linux Debian Stretch (PC)
OK Safari 11.1.1 (13605.2.8) (Mac mini)

The question: is there a way to reliably pause/resume CSS animations when switching tabs?

 


You can use jQuery to achieve the desired result. However, Javascript can also used here.

$(function() {   $(window).on('focus', function() {     $('#test').css('animation-play-state', 'running');   });    $(window).on('blur', function() {     $('#test').css('animation-play-state', 'paused');   }); }); 

Another way using CSS classes:

.paused {   animation-play-state: paused !important;   -webkit-animation-play-state: paused !important;   -moz-animation-play-state: paused !important; } 
let box = document.querySelector('#test');  window.addEventListener('focus', function() {   box.classList.remove('paused'); });  window.addEventListener('blur', function() {   box.classList.add('paused'); }); 

The above two ways doesn't works in the iframe of CodePen, JSFiddle, JSBin, etc; a possible reason is provided at the end of post. But, here is a video link displaying how the code works in debug mode of CodePen.

Live Example

Confirmed in:

  1. Google Chrome v70.0.3538.67 (Official Build) (32-bit)

  2. Firefox Quantum v62.0.3 (32-bit)

  3. Internet Explorer v11+

jQuery: Version 3.3.1 (Minified)


Possible reason of why the code doesn't works inside iframe:

When I tried accessing the root window (aka parent object, note that it is not the iframe window), the following error logged in the console:

Pause/resume CSS animations when switching tabs

And this means that I can't access the root window of CodePen and others. Therefore, if I can't access it, I can't add any event listeners to it.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: