Il semble que requestAnimationFrame
c'est la manière de facto d'animer les choses maintenant. Cela a plutôt bien fonctionné pour moi pour la plupart, mais pour le moment, j'essaie de faire des animations de canevas et je me demandais: y a-t-il un moyen de m'assurer qu'il fonctionne à un certain fps? Je comprends que le but de la rAF est d'avoir des animations toujours fluides, et je pourrais courir le risque de rendre mon animation saccadée, mais pour le moment, il semble fonctionner à des vitesses radicalement différentes de manière assez arbitraire, et je me demande s'il existe un moyen de combattre cela en quelque sorte.
J'utiliserais setInterval
mais je veux les optimisations offertes par rAF (en particulier l'arrêt automatique lorsque l'onglet est mis au point).
Au cas où quelqu'un voudrait regarder mon code, c'est à peu près:
animateFlash: function() {
ctx_fg.clearRect(0,0,canvasWidth,canvasHeight);
ctx_fg.fillStyle = 'rgba(177,39,116,1)';
ctx_fg.strokeStyle = 'none';
ctx_fg.beginPath();
for(var i in nodes) {
nodes[i].drawFlash();
}
ctx_fg.fill();
ctx_fg.closePath();
var instance = this;
var rafID = requestAnimationFrame(function(){
instance.animateFlash();
})
var unfinishedNodes = nodes.filter(function(elem){
return elem.timer < timerMax;
});
if(unfinishedNodes.length === 0) {
console.log("done");
cancelAnimationFrame(rafID);
instance.animate();
}
}
Où Node.drawFlash () est juste un code qui détermine le rayon en fonction d'une variable de compteur, puis dessine un cercle.
la source
requestAnimationFrame
est (comme son nom l'indique) de demander une image d'animation uniquement lorsque cela est nécessaire. Supposons que vous montriez un canevas noir statique, vous devriez obtenir 0 fps car aucun nouveau cadre n'est nécessaire. Mais si vous affichez une animation qui nécessite 60 ips, vous devriez l'obtenir également.rAF
permet juste de "sauter" les trames inutiles, puis de sauvegarder le CPU.Réponses:
Comment limiter la demande d'AnimationFrame à une fréquence d'images spécifique
Limitation de démo à 5 FPS: http://jsfiddle.net/m1erickson/CtsY3/
Cette méthode fonctionne en testant le temps écoulé depuis l'exécution de la dernière boucle de trame.
Votre code de dessin s'exécute uniquement lorsque votre intervalle FPS spécifié s'est écoulé.
La première partie du code définit certaines variables utilisées pour calculer le temps écoulé.
Et ce code est la boucle requestAnimationFrame réelle qui dessine à votre FPS spécifié.
la source
Mise à jour 2016/6
Le problème de la limitation de la fréquence d'images est que l'écran a un taux de mise à jour constant, généralement 60 FPS.
Si nous voulons 24 FPS, nous n'obtiendrons jamais les véritables 24 fps à l'écran, nous pouvons le chronométrer en tant que tel mais ne pas le montrer car le moniteur ne peut afficher que des images synchronisées à 15 fps, 30 fps ou 60 fps (certains moniteurs également 120 fps ).
Cependant, à des fins de synchronisation, nous pouvons calculer et mettre à jour lorsque cela est possible.
Vous pouvez créer toute la logique de contrôle de la fréquence d'images en encapsulant les calculs et les rappels dans un objet:
Ensuite, ajoutez un contrôleur et un code de configuration:
Usage
Cela devient très simple - maintenant, tout ce que nous avons à faire est de créer une instance en définissant la fonction de rappel et la fréquence d'images souhaitée comme ceci:
Puis démarrez (ce qui pourrait être le comportement par défaut si vous le souhaitez):
Voilà, toute la logique est gérée en interne.
Démo
Ancienne réponse
L'objectif principal de
requestAnimationFrame
est de synchroniser les mises à jour avec la fréquence de rafraîchissement du moniteur. Cela vous obligera à animer au FPS du moniteur ou à un facteur de celui-ci (c'est-à-dire 60, 30, 15 FPS pour un taux de rafraîchissement typique à 60 Hz).Si vous voulez un FPS plus arbitraire, il est inutile d'utiliser rAF car la fréquence d'images ne correspondra jamais à la fréquence de mise à jour du moniteur de toute façon (juste une image ici et là) qui ne peut tout simplement pas vous donner une animation fluide (comme avec tous les resynchronisations d'image ) et vous pouvez aussi bien utiliser
setTimeout
ou à lasetInterval
place.Il s'agit également d'un problème bien connu dans l'industrie de la vidéo professionnelle lorsque vous souhaitez lire une vidéo à un FPS différent de celui de l'appareil qui l'affiche. De nombreuses techniques ont été utilisées, telles que le mélange d'images et la recréation complexe d'images intermédiaires basées sur des vecteurs de mouvement, mais avec le canevas, ces techniques ne sont pas disponibles et le résultat sera toujours une vidéo saccadée.
La raison pour laquelle nous plaçons en
setTimeout
premier (et pourquoi une place enrAF
premier lorsqu'un poly-fill est utilisé) est que ce sera plus précis que lesetTimeout
mettra en file d'attente un événement immédiatement lorsque la boucle démarre, de sorte que peu importe combien de temps le code restant utilisera (à condition qu'il ne dépasse pas l'intervalle de temporisation) le prochain appel sera à l'intervalle qu'il représente (pour la rAF pure, ce n'est pas essentiel car rAF essaiera de sauter sur la trame suivante dans tous les cas).Il convient également de noter que le placer en premier risque également de risquer que les appels s'empilent comme avec
setInterval
.setInterval
peut être légèrement plus précis pour cette utilisation.Et vous pouvez utiliser à la
setInterval
place en dehors de la boucle pour faire de même.Et pour arrêter la boucle:
Afin de réduire la fréquence d'images lorsque l'onglet devient flou, vous pouvez ajouter un facteur comme celui-ci:
De cette façon, vous pouvez réduire le FPS à 1/4, etc.
la source
requestAnimationFrame
est de synchroniser les opérations DOM (lecture / écriture) donc ne pas l'utiliser nuira aux performances lors de l'accès au DOM, car les opérations ne seront pas mises en file d'attente pour être exécutées ensemble et forceront inutilement à repeindre la mise en page.Je suggère d'envelopper votre appel
requestAnimationFrame
dans unsetTimeout
:Vous devez appeler
requestAnimationFrame
de l'intérieursetTimeout
, plutôt que l'inverse, carrequestAnimationFrame
vous programmez votre fonction pour qu'elle s'exécute juste avant le prochain repeint, et si vous retardez votre mise à jour,setTimeout
vous aurez manqué cette fenêtre de temps. Cependant, faire l'inverse est judicieux, car vous attendez simplement un certain temps avant de faire la demande.la source
Ce sont toutes de bonnes idées en théorie, jusqu'à ce que vous approfondissiez. Le problème est que vous ne pouvez pas étrangler une RAF sans la désynchroniser, ce qui va à l'encontre de son objectif même d'exister. Vous le laissez donc fonctionner à pleine vitesse et mettez à jour vos données dans une boucle séparée , ou même un thread séparé!
Oui, je l'ai dit. Vous pouvez faire du JavaScript multi-thread dans le navigateur!
Il y a deux méthodes que je connais qui fonctionnent extrêmement bien sans jank, en utilisant beaucoup moins de jus et en créant moins de chaleur. Une synchronisation précise à l'échelle humaine et une efficacité de la machine en sont le résultat.
Toutes mes excuses si c'est un peu verbeux, mais voilà ...
Méthode 1: mettre à jour les données via setInterval et les graphiques via RAF.
Utilisez un setInterval distinct pour mettre à jour les valeurs de translation et de rotation, la physique, les collisions, etc. Conservez ces valeurs dans un objet pour chaque élément animé. Affectez la chaîne de transformation à une variable de l'objet à chaque setInterval 'frame'. Conservez ces objets dans un tableau. Réglez votre intervalle sur vos fps souhaités en ms: ms = (1000 / fps). Cela permet de maintenir une horloge constante qui permet les mêmes fps sur n'importe quel appareil, quelle que soit la vitesse de la RAF. N'affectez pas les transformations aux éléments ici!
Dans une boucle requestAnimationFrame, parcourez votre tableau avec une boucle for old-school - n'utilisez pas les nouveaux formulaires ici, ils sont lents!
Dans votre fonction rafUpdate, récupérez la chaîne de transformation de votre objet js dans le tableau, et son identifiant d'éléments. Vous devriez déjà avoir vos éléments «sprite» attachés à une variable ou facilement accessibles par d'autres moyens pour ne pas perdre de temps à les «obtenir» dans le RAF. Les garder dans un objet nommé d'après leur identifiant html fonctionne plutôt bien. Configurez cette partie avant même qu'elle n'entre dans votre SI ou RAF.
Utilisez la RAF pour mettre à jour vos transformations uniquement , utilisez uniquement des transformations 3D (même pour 2d) et définissez css "will-change: transform;" sur des éléments qui vont changer. Cela maintient autant que possible vos transformations synchronisées avec le taux de rafraîchissement natif, active le GPU et indique au navigateur où se concentrer le plus.
Donc, vous devriez avoir quelque chose comme ce pseudocode ...
Cela permet de synchroniser vos mises à jour des objets de données et des chaînes de transformation à la fréquence d'images souhaitée dans le SI, et les affectations de transformation réelles dans le RAF synchronisées avec la fréquence de rafraîchissement du GPU. Ainsi, les mises à jour graphiques réelles ne sont que dans le RAF, mais les modifications apportées aux données et la construction de la chaîne de transformation sont dans le SI, donc pas de jankies mais le «temps» s'écoule à la fréquence d'images souhaitée.
Couler:
Méthode 2. Mettez le SI dans un web-worker. Celui-ci est FAAAST et lisse!
Identique à la méthode 1, mais placez le SI dans Web-worker. Il fonctionnera alors sur un thread totalement séparé, laissant la page uniquement pour la RAF et l'interface utilisateur. Passez le tableau de sprites d'avant en arrière en tant qu '«objet transférable». C'est buko rapide. Le clonage ou la sérialisation ne prend pas de temps, mais ce n'est pas comme passer par référence dans la mesure où la référence de l'autre côté est détruite, vous devrez donc faire passer les deux côtés de l'autre côté, et ne les mettre à jour que lorsqu'elles sont présentes, trier comme passer une note dans les deux sens avec votre petite amie au lycée.
Un seul peut lire et écrire à la fois. C'est très bien tant qu'ils vérifient si ce n'est pas indéfini pour éviter une erreur. La RAF est RAPIDE et la réactivera immédiatement, puis passera par un tas de trames GPU en vérifiant simplement si elle a déjà été renvoyée. Le SI dans le web-worker aura le tableau de sprites la plupart du temps, et mettra à jour les données de position, de mouvement et de physique, ainsi que la création de la nouvelle chaîne de transformation, puis la transmettra au RAF dans la page.
C'est le moyen le plus rapide que je connaisse pour animer des éléments via un script. Les deux fonctions seront exécutées comme deux programmes séparés, sur deux threads séparés, profitant des processeurs multicœurs d'une manière qu'un seul script js ne le fait pas. Animation javascript multi-thread.
Et il le fera sans à-coups, mais à la fréquence d'images réelle spécifiée, avec très peu de divergence.
Résultat:
L'une ou l'autre de ces deux méthodes garantira que votre script s'exécutera à la même vitesse sur n'importe quel PC, téléphone, tablette, etc. (dans les limites des capacités de l'appareil et du navigateur, bien sûr).
la source
visibilitychange
événement.Comment accélérer facilement à un FPS spécifique:
Source: Une explication détaillée des boucles et du timing du jeu JavaScript par Isaac Sukin
la source
Saut à la corde requestAnimationFrame la cause pas lisse (souhaitée) à l' animation personnalisée fps.
Code original par @tavnab.
la source
la source
Je le fais toujours de cette manière très simple sans jouer avec les horodatages:
la source
Voici une bonne explication que j'ai trouvée: CreativeJS.com , pour encapsuler un appel setTimeou) dans la fonction transmise à requestAnimationFrame. Mon souci avec une requestionAnimationFrame "simple" serait, "Et si je ne voulais que l'animation trois fois par seconde?" Même avec requestAnimationFrame (par opposition à setTimeout) est qu'il reste gaspille (une certaine) quantité d '"énergie" (ce qui signifie que le code du navigateur fait quelque chose et ralentit peut-être le système) 60 ou 120 ou autant de fois par seconde, comme par opposition à seulement deux ou trois fois par seconde (comme vous voudrez peut-être).
La plupart du temps, j'exécute mes navigateurs avec JavaScript désactivé pour cette raison. Mais j'utilise Yosemite 10.10.3, et je pense qu'il y a une sorte de problème de minuterie - du moins sur mon ancien système (relativement vieux - c'est-à-dire 2011).
la source