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!
for(var i=0; i<sprite.length-1; i++){ rafUpdate(sprite[i]); }
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 ...
// refs to elements to be transformed, kept in an array
var element = [
mario: document.getElementById('mario'),
luigi: document.getElementById('luigi')
//...etc.
]
var sprite = [ // read/write this with SI. read-only from RAF
mario: { id: mario ....physics data, id, and updated transform string (from SI) here },
luigi: { id: luigi .....same }
//...and so forth
] // also kept in an array (for efficient iteration)
//update one sprite js object
//data manipulation, CPU tasks for each sprite object
//(physics, collisions, and transform-string updates here.)
//pass the object (by reference).
var SIupdate = function(object){
// get pos/rot and update with movement
object.pos.x += object.mov.pos.x; // example, motion along x axis
// and so on for y and z movement
// and xyz rotational motion, scripted scaling etc
// build transform string ie
object.transform =
'translate3d('+
object.pos.x+','+
object.pos.y+','+
object.pos.z+
') '+
// assign rotations, order depends on purpose and set-up.
'rotationZ('+object.rot.z+') '+
'rotationY('+object.rot.y+') '+
'rotationX('+object.rot.x+') '+
'scale3d('.... if desired
; //...etc. include
}
var fps = 30; //desired controlled frame-rate
// CPU TASKS - SI psuedo-frame data manipulation
setInterval(function(){
// update each objects data
for(var i=0; i<sprite.length-1; i++){ SIupdate(sprite[i]); }
},1000/fps); // note ms = 1000/fps
// GPU TASKS - RAF callback, real frame graphics updates only
var rAf = function(){
// update each objects graphics
for(var i=0; i<sprite.length-1; i++){ rAF.update(sprite[i]) }
window.requestAnimationFrame(rAF); // loop
}
// assign new transform to sprite's element, only if it's transform has changed.
rAF.update = function(object){
if(object.old_transform !== object.transform){
element[object.id].style.transform = transform;
object.old_transform = object.transform;
}
}
window.requestAnimationFrame(rAF); // begin RAF
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:
[setup js sprite objects and html element object references]
[setup RAF and SI single-object update functions]
[start SI at percieved/ideal frame-rate]
[iterate through js objects, update data transform string for each]
[loop back to SI]
[start RAF loop]
[iterate through js objects, read object's transform string and assign it to it's html element]
[loop back to RAF]
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).
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.