web-dev-qa-db-fra.com

Suspendre/reprendre les animations CSS lors du changement d'onglets

J'ai un tas d'animations CSS de longue durée sur une page. Je souhaite les mettre en pause lorsqu'un utilisateur passe à un autre onglet et les reprend lorsque l'utilisateur revient à l'onglet d'origine. Par souci de simplicité, je ne vise pas pour l'instant une solution multi-navigateurs; le faire fonctionner dans Chrome devrait être suffisant.

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>

Le code ci-dessus déplace un rectangle rouge horizontalement. Il faut 5 secondes au rectangle pour terminer l'animation.

Le problème: après le début de l'animation, passez à un autre onglet du navigateur. Après un certain temps (plus de 5 secondes), revenez au premier onglet.

Résultat attendu: le rectangle poursuit sa trajectoire à partir du point où il s'était arrêté.

Résultat actuel: la plupart du temps, le rectangle apparaît dans sa destination finale et s'arrête. Parfois, cela fonctionne comme prévu. La vidéo illustre le problème.

J'ai joué avec différentes valeurs pour animation-fill-mode et animation-timing-function, mais le résultat était toujours le même. Comme rv7 a fait remarquer , le partage des exemples dans CodePen, JSFiddle, JSBin et stackoverflow JS affecte les résultats, il est donc préférable de tester directement sur une page statique sur un serveur HTTP local (ou à l'aide de liens ci-dessous).

Pour plus de commodité, j'ai déployé le code ci-dessus pour Heroku . L'application est un serveur HTTP statique de nodejs, qui s'exécute sur un abonnement gratuit. Le chargement de la page peut prendre jusqu'à 5 minutes pour la première fois. Test des résultats par rapport à cette page: 

FAIL Chrome 64.0.3282.167 (version officielle) (64 bits) Linux Debian Stretch (PC)
FAIL Chrome 70.0.3538.67 (version officielle) (64 bits) Windows 10 (PC)
FAIL Chrome 70.0.3538.77 (version officielle) (32 bits) Windows 7 (PC)
FAIL Chrome 70.0.3538.77 (version officielle) (64 bits) OSX 10.13.5 (Mac mini)
FAIL FF Quantum 60.2.2esr (64 bits) Linux Debian Stretch (PC)
OK Safari 11.1.1 (13605.2.8) (Mac mini)

Les API de visibilité de page } peuvent être remplacées par les événements window focus et blur tels que:

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

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

Ce remplacement n'est pas équivalent cependant. Si une page contient des IFRAME, leur interaction déclenchera des événements focus et blur dans la fenêtre principale. L'exécution d'un code de commutation de tabulation dans ce cas n'est pas correcte. Cela peut toujours être une option dans certains cas, j'ai donc déployé une page pour tester ici . Les résultats sont légèrement meilleurs: 

FAIL Chrome 64.0.3282.167 (version officielle) (64 bits) Linux Debian Stretch (PC)
FAIL Chrome 70.0.3538.67 (version officielle) (64 bits) Windows 10 (PC)
FAIL Chrome 70.0.3538.77 (version officielle) (32 bits) Windows 7 (PC)
FAIL Chrome 70.0.3538.77 (version officielle) (64 bits) OSX 10.13.5 (Mac mini)
OK FF Quantum 60.2.2esr (64 bits) Linux Debian Stretch (PC)
OK Safari 11.1.1 (13605.2.8) (Mac mini)
Cette version échoue plus souvent dans Chrome 64 bits que dans Chrome 32 bits. Dans Chrome 32 bits, je n'ai eu qu'un seul échec après environ 20 tentatives. Cela peut être lié au fait que Chrome 32 bits est installé sur un matériel ancien.

La question: existe-t-il un moyen de suspendre/reprendre de manière fiable les animations CSS lors du changement d'onglet?

18
Femel

Vous pouvez choisir les événements focus ET blur plutôt que visibilitychange en raison de la prise en charge améliorée du navigateur par rapport à l'ancien!

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

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

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

Alternativement, vous pouvez aussi le faire en utilisant des classes 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');
});

Les deux méthodes ci-dessus ne fonctionnent pas dans le iframe de CodePen, JSFiddle, JSBin, etc. une raison possible est fournie à la fin du message. Mais, voici un lien vidéo montrant comment le code fonctionne en mode débogage de CodePen .

Exemple en direct

Confirmé dans:

  1. Google Chrome v70.0.3538.67 (version officielle) (32 bits)

  2. Firefox Quantum v62.0.3 (32 bits)

  3. Internet Explorer v11 +


Raison possible pour laquelle le code ne fonctionne pas dans iframe:

Lorsque j'ai essayé d'accéder à l'objet root window (alias parent object , notez qu'il ne s'agit pas du iframe window), l'erreur suivante s'est produite dans la console:

 enter image description here

Et cela signifie que je ne peux pas accéder au root window de CodePen et des autres. Par conséquent, si je ne peux pas y accéder, je ne peux pas y ajouter d'écouteurs d'événement.

9
rv7

Pour moi, votre exemple fonctionne la plupart du temps.

Cependant, j'utiliserais des pourcentages et ajouterais également un point de départ dans les images clés:

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

De plus, la détection des onglets inactifs ne se déclenche pas toujours. Une fenêtre inactive semble mieux fonctionner: (avec 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

Vous n’avez pas besoin d’utiliser de classes ni d’activer/désactiver ces classes pour obtenir le résultat attendu . Ici, téléchargez ce fichier Gist html et essayez à votre fin, voyons si cela fonctionne.

Vous devez définir le animation-play-state lors de l'écriture de CSS pour le test div sur paused.

Une fois la fenêtre chargée, définissez le animation-play-state sur running, puis définissez-les à nouveau en fonction des événements blur et focus.

Aussi J'ai remarqué que l'utilisation du préfixe du navigateur (-webkit-animation-play-state) m'a aidé à obtenir le résultat attendu, assurez-vous de les utiliser. J'ai testé sur Firefox et Chrome, fonctionne bien pour moi.

Remarque: Cela pourrait ne pas fonctionner sur cet extrait de code ici (car l'extrait ne se trouve pas dans la fenêtre principale de cet onglet en cours). Téléchargez le lien Gist fourni ci-dessus et vérifiez de votre côté.

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 Ahmed