切换标签时暂停/恢复CSS动画

时间:2018-10-19 13:01:02

标签: javascript css animation tabs css-animations

我在页面上有一堆长时间运行的CSS动画。我想在用户切换到另一个选项卡时暂停它们,并在用户再次回到原始选项卡时恢复它们。为了简单起见,我目前不打算提供跨浏览器的解决方案。使它在Chrome中运行就足够了。

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>

上面的代码水平移动一个红色矩形。矩形需要5秒钟才能完成动画。

问题:动画开始后,切换到另一个浏览器标签;一段时间(超过5秒)后,请切换回第一个标签。

预期结果:矩形从上次中断的位置开始沿其路径移动。

实际结果:大多数情况下,矩形会出现在其最终目的地并停止。有时它会按预期工作。 The video演示了问题。

我在animation-fill-modeanimation-timing-function上使用了不同的值,但结果始终相同。由于rv7 pointed out,在CodePen,JSFiddle,JSBin和stackoverflow JS工具中共享示例会影响结果,因此最好直接对本地HTTP服务器上的静态页面进行测试(或使用下面的链接)。

为方便起见,我已经部署了the code above to Heroku。该应用程序是一个静态的nodejs HTTP服务器,它以免费订阅方式运行,因此首次加载该页面最多可能需要5分钟。针对此页面的测试结果:

  

FAIL Chrome 64.0.3282.167(官方内部版本)(64位)Linux Debian Stretch(PC)
  失败Chrome 70.0.3538.67(Official Build)(64位)Windows 10(PC)
  失败Chrome 70.0.3538.77(Official Build)(32位)Windows 7(PC)
  失败Chrome 70.0.3538.77(Official Build)(64位)OSX 10.13.5(Mac mini)
  FAIL FF Quantum 60.2.2esr(64位)Linux Debian Stretch(PC)
  OK Safari 11.1.1(13605.2.8)(Mac mini)

page visibility API可以替换为窗口focusblur事件,如下所示:

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

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

但是,这种替换并不等效。如果页面包含IFRAME,则与它们的内容进行交互将在主窗口上触发focusblur事件。在这种情况下执行任何选项卡切换代码是不正确的。在某些情况下,这仍然是一个选项,因此我部署了一个页面来测试here。结果稍微好一点:

  

FAIL Chrome 64.0.3282.167(官方内部版本)(64位)Linux Debian Stretch(PC)
  失败Chrome 70.0.3538.67(Official Build)(64位)Windows 10(PC)
  失败Chrome 70.0.3538.77(Official Build)(32位)Windows 7(PC)
  失败Chrome 70.0.3538.77(Official Build)(64位)OSX 10.13.5(Mac mini)
  OK FF Quantum 60.2.2esr(64位)Linux Debian Stretch(PC)
  OK Safari 11.1.1(13605.2.8)(Mac mini)
  在Chrome 64位系统中,此版本失败的频率高于在Chrome 32位系统中。在32位Chrome浏览器中,经过20次成功尝试后,我只有1次失败。这可能与以下事实有关:Chrome 32位安装在较旧的硬件上。

问题:在切换标签页时,有没有办法可靠地暂停/恢复CSS动画?

3 个答案:

答案 0 :(得分:9)

您可以使用focus and blur events而不是visibilitychange event,因为前者对浏览器的支持更好!

let box = document.querySelector('#test');

window.addEventListener('focus', function() {
  box.style.animationPlayState = 'paused';
});

window.addEventListener('blur', function() {
  box.style.animationPlayState = 'running';
});

或者,您也可以使用CSS类执行此操作:

.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');
});

以上两种方法在CodePen,JSFiddle,JSBin等的 iframe 中均无效;帖子结尾处提供了可能的原因。但是,here is a video link displaying how the code works in debug mode of CodePen

Live Example

  

中确认:

     
      
  1. Google Chrome v70.0.3538.67(正式版本)(32位)

  2.   
  3. Firefox Quantum v62.0.3(32位)

  4.   
  5. Internet Explorer v11 +

  6.   

代码无法在iframe内运行的可能原因

当我尝试访问 root window (又名parent object)时,请注意,它不是 iframe window ),以下内容控制台中记录了错误:

enter image description here

这意味着我无法访问CodePen等的 root window 。因此,如果我无法访问它,则无法向其添加任何事件侦听器。

答案 1 :(得分:0)

对我来说,您的示例在大多数情况下都有效。

但是,我将使用百分比并在关键帧中添加起点:

@keyframes move {
  0% { 
    left: 0px;
  }
  100% {
    left: 500px
  }
}

此外,不活动的标签检测并不总是触发。一个不活动的窗口似乎更好地工作:(使用jQuery)

$(window).on("blur focus", function(e) {
    var prevType = $(this).data("prevType");

    if (prevType != e.type) {
        var element = document.getElementById("test");
        switch (e.type) {
            case "blur":
                element.style["animation-play-state"] = "paused";
                break;
            case "focus":
                element.style["animation-play-state"] = "running";
                break;
        }
    }

    $(this).data("prevType", e.type);
})

答案 2 :(得分:0)

您无需使用任何类或切换它们即可获得预期的结果。 在这里,下载this gist html文件,然后尝试尝试,看看是否可行。

在将animation-play-state div的CSS编写为test时,需要设置paused

窗口加载后,将animation-play-state设置为running,然后根据blurfocus事件再次设置它们。

我注意到使用浏览器前缀(-webkit-animation-play-state)帮助我获得了预期的结果,因此请务必使用它们。我在Firefox和Chrome上进行了测试,对我来说效果很好。

注意:这可能不适用于此代码段(因为该代码段不在当前选项卡的主窗口中)。下载上面提供的要点链接,然后检查一下。

window.onload = function() {
  // not mentioning the state at all does not provide the expected result, so you need to set 
  // the state to paused and set it to running on window load
  document.getElementById('test').style.webkitAnimationPlayState = "running";
  document.getElementById('test').style.animationPlayState = 'running';
}

window.onblur = function() {
  document.title = 'paused now';
  document.getElementById('test').style.webkitAnimationPlayState = "paused";
  document.getElementById('test').style.animationPlayState = 'paused';
  document.getElementById('test').style.background = 'pink'; // for testing
}

window.onfocus = function() {
  document.title = 'running now';
  document.getElementById('test').style.webkitAnimationPlayState = "running";
  document.getElementById('test').style.animationPlayState = 'running';
  document.getElementById('test').style.background = 'red'; // for testing
}
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;
  /* add the state here and set it to running on window load */
  -webkit-animation-play-state: paused;
  animation-play-state: paused;
}

@keyframes move {
  to {
    left: 90vw
  }
}
<div id="test"></div>