title: Pause/resume CSS animations when switching tabs
body: <p>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.</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>document.addEventListener("visibilitychange", function() {
    if (document.hidden) {
      document.querySelector("#test").classList.add("paused");
    } else {
      document.querySelector("#test").classList.remove("paused");    
    }
  });</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>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;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;div id="test"&gt;&lt;/div&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <p>The code above moves a red rectangle horizontally. It takes 5 seconds for the rectangle to complete the animation.</p>
  
  <p><strong>The problem:</strong> after the animation starts, switch to another browser tab; after some period of time (longer than 5 seconds) switch back to the first tab.</p>
  
  <p><strong>Expected result:</strong> the rectangle continues its path from the point where it left off.</p>
  
  <p><strong>Actual result:</strong> most of the times the rectangle appears in its final destination and stops. Sometimes it works as expected. <a href="https://www.dropbox.com/s/ydytr3wocesj8ru/animation-pause-problem.mp4?dl=0" rel="noreferrer">The video</a> demonstrates the problem.</p>
  
  <p>I played with different values for <code>animation-fill-mode</code> and <code>animation-timing-function</code>, but the result was always the same. As rv7 <a href="https://stackoverflow.com/a/52918455/1386109">pointed out</a>, 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).</p>
  
  <p>For convenience I've deployed <a href="https://tabs-animations-pause-resume.herokuapp.com/visibility-api.html" rel="noreferrer">the code above to Heroku</a>. 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:  </p>
  
  <blockquote>
    <p>FAIL Chrome 64.0.3282.167 (Official Build) (64-bit) Linux Debian Stretch (PC)<br>
    FAIL Chrome 70.0.3538.67 (Official Build) (64-bit) Windows 10 (PC)<br>
    FAIL Chrome 70.0.3538.77 (Official Build) (32-bit) Windows 7 (PC)<br>
    FAIL Chrome 70.0.3538.77 (Official Build) (64-bit) OSX 10.13.5 (Mac mini)<br>
    FAIL FF Quantum 60.2.2esr (64-bit) Linux Debian Stretch (PC)<br>
    OK Safari 11.1.1 (13605.2.8) (Mac mini)</p>
  </blockquote>
  
  <p>The <a href="https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API" rel="noreferrer">page visibility API</a> can be replaced with window <code>focus</code> and <code>blur</code> events like this:</p>
  
  <pre><code>window.addEventListener("focus", function() {
      document.querySelector("#test").classList.remove("paused");        
  });
  
  window.addEventListener("blur", function() {
      document.querySelector("#test").classList.add("paused");
  });
  </code></pre>
  
  <p>This replacement isn't equivalent however. If a page contains IFRAMEs, interacting with their contents will trigger <code>focus</code> and <code>blur</code> 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 <a href="https://tabs-animations-pause-resume.herokuapp.com/focus-blur.html" rel="noreferrer">here</a>. The results are slightly better:  </p>
  
  <blockquote>
    <p>FAIL Chrome 64.0.3282.167 (Official Build) (64-bit) Linux Debian Stretch (PC)<br>
    FAIL Chrome 70.0.3538.67 (Official Build) (64-bit) Windows 10 (PC)<br>
    FAIL Chrome 70.0.3538.77 (Official Build) (32-bit) Windows 7 (PC)<br>
    FAIL Chrome 70.0.3538.77 (Official Build) (64-bit) OSX 10.13.5 (Mac mini)<br>
    OK FF Quantum 60.2.2esr (64-bit) Linux Debian Stretch (PC)<br>
    OK Safari 11.1.1 (13605.2.8) (Mac mini)<br>
    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.</p>
  </blockquote>
  
  <p><strong>The question:</strong> is there a way to reliably pause/resume CSS animations when switching tabs?</p>