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) (32-bit) Windows 7 (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) (32-bit) Windows 7 (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)
This version fails more often in Chrome 64-bit than in Chrome 32-bit. In Chrome 32-bit I got just 1 failure after ~20 successful attempts. This might be related to the fact that Chrome 32-bit is installed on older hardware.
The question: is there a way to reliably pause/resume CSS animations when switching tabs?