web-dev-qa-db-ja.com

タブを切り替えるときにCSSアニメーションを一時停止/再開する

ページに長時間実行される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秒以上)、最初のタブに戻ります。

期待される結果:長方形は、中断したところからパスを続けます。

実際の結果:ほとんどの場合、長方形は最終的な目的地に現れて停止します。時々それは期待通りに機能します。 ビデオ は問題を示しています。

animation-fill-modeanimation-timing-functionに異なる値を指定してプレイしましたが、結果は常に同じでした。 rv7 指摘 のように、CodePen、JSFiddle、JSBin、stackoverflow JSツールで例を共有すると結果に影響するため、ローカルHTTPサーバーの静的ページに対して直接テストする(または以下のリンクを使用する)方が良い。

便宜上、私は Herokuへの上記のコード をデプロイしました。アプリは静的nodejs HTTPサーバーであり、無料サブスクリプションで実行されるため、ページが初めて読み込まれるまでに最大5分かかる場合があります。このページに対するテスト結果:

FAIL Chrome 64.0.3282.167(公式ビルド)(64ビット)Linux Debian Stretch(PC)
FAIL Chrome 70.0.3538.67(公式ビルド)(64ビット)Windows 10(PC)
FAIL Chrome 70.0.3538.77(公式ビルド)(32ビット)Windows 7(PC)
FAIL Chrome 70.0.3538.77(公式ビルド)(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)

ページ可視性API は、次のようなwindow focusおよびblurイベントに置き換えることができます。

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

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

ただし、この置き換えは同等ではありません。ページにIFRAMEが含まれている場合、そのコンテンツを操作すると、メインウィンドウでfocusおよびblurイベントがトリガーされます。この場合、タブ切り替えコードの実行は正しくありません。これは場合によってはオプションである可能性があるため、テスト用のページ here をデプロイしました。結果は少し良くなります:

FAIL Chrome 64.0.3282.167(公式ビルド)(64ビット)Linux Debian Stretch(PC)
FAIL Chrome 70.0.3538.67(公式ビルド)(64ビット)Windows 10(PC)
FAIL Chrome 70.0.3538.77(公式ビルド)(32ビット)Windows 7(PC)
FAIL Chrome 70.0.3538.77(公式ビルド)(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-bitよりもChrome 32-bitで失敗します。InChrome = 32ビット〜20回の試行が成功した後、1回だけ失敗しました。これは、Chrome 32ビットが古いハードウェアにインストールされているという事実に関連している可能性があります。

質問:タブを切り替えるときにCSSアニメーションを確実に一時停止/再開する方法はありますか?

18
eugenesqr

focus event ではなく blurおよびvisibilitychange events を使用できます。これは、前者!

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

上記の2つの方法は、CodePen、JSFiddle、JSBinなどのiframeでは機能しません。考えられる理由は、投稿の最後に記載されています。ただし、 CodePenのデバッグモードでコードがどのように機能するかを示すビデオリンクがあります

実例

確認済み

  1. Google Chrome v70.0.3538.67(公式ビルド)(32ビット)

  2. Firefox Quantum v62.0.3(32ビット)

  3. Internet Explorer v11以降


iframe内でコードが機能しない理由の可能性:

root window(aka parentオブジェクトにアクセスしましたiframe window)ではないことに注意してください。次のエラーがコンソール:

enter image description here

これは、CodePenなどのroot windowにアクセスできないことを意味します。したがって、アクセスできない場合は、イベントリスナーを追加できません。

10
Rahul Verma

私にとって、あなたの例はほとんどの場合うまくいきます。

ただし、パーセンテージを使用し、キーフレームに開始点を追加します。

@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);
})
0
Jobse

期待される結果を得るために、クラスを使用したり、クラスを切り替えたりする必要はありません。ここで、 this Gist htmlファイルをダウンロードして、最後に試してみてください。

test divのCSSをpausedに書き込むときに、animation-play-stateを設定する必要があります。

ウィンドウがロードされたら、animation-play-staterunningに設定し、blurおよびfocusイベントに従って再度設定します。

また、ブラウザのプレフィックス(-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>
0
Towkir