Todas estas son buenas ideas en teoría, hasta que profundices. El problema es que no se puede estrangular un RAF sin desincronizarlo, lo que frustra su propósito de existir. ¡Entonces lo deja correr a toda velocidad y actualiza sus datos en un bucle separado , o incluso en un hilo separado!
Sí lo dije. Usted puede hacer de múltiples hebras de JavaScript en el navegador!
Sé que hay dos métodos que funcionan extremadamente bien sin jank, usando mucho menos jugo y creando menos calor. El resultado neto es el tiempo preciso a escala humana y la eficiencia de la máquina.
Disculpas si esto es un poco prolijo, pero aquí va ...
Método 1: Actualice los datos a través de setInterval y los gráficos a través de RAF.
Use un setInterval separado para actualizar los valores de traslación y rotación, física, colisiones, etc. Mantenga esos valores en un objeto para cada elemento animado. Asigne la cadena de transformación a una variable en el objeto cada 'marco' setInterval. Mantenga estos objetos en una matriz. Establezca su intervalo a sus fps deseados en ms: ms = (1000 / fps). Esto mantiene un reloj constante que permite los mismos fps en cualquier dispositivo, independientemente de la velocidad RAF. ¡No asigne las transformaciones a los elementos aquí!
En un bucle requestAnimationFrame, recorra su matriz con un bucle for de la vieja escuela; no use los formularios más nuevos aquí, ¡son lentos!
for(var i=0; i<sprite.length-1; i++){ rafUpdate(sprite[i]); }
En su función rafUpdate, obtenga la cadena de transformación de su objeto js en la matriz y su id de elementos. Ya debería tener sus elementos 'sprite' unidos a una variable o de fácil acceso a través de otros medios para que no pierda tiempo 'metiéndolos' en la RAF. Mantenerlos en un objeto con el nombre de su identificación html funciona bastante bien. Configure esa parte incluso antes de que entre en su SI o RAF.
Use la RAF para actualizar solo sus transformaciones , use solo transformaciones 3D (incluso para 2d) y configure css "will-change: transform;" en elementos que cambiarán. Esto mantiene sus transformaciones sincronizadas con la frecuencia de actualización nativa tanto como sea posible, activa la GPU y le dice al navegador dónde concentrarse más.
Entonces deberías tener algo como este pseudocódigo ...
// 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
Esto mantiene sus actualizaciones de los objetos de datos y las cadenas de transformación sincronizadas con la frecuencia de 'fotograma' deseada en el SI, y las asignaciones de transformación reales en la RAF sincronizadas con la frecuencia de actualización de la GPU. Por lo tanto, las actualizaciones de gráficos reales solo están en la RAF, pero los cambios en los datos y la construcción de la cadena de transformación están en el SI, por lo tanto, no hay jankies sino que el 'tiempo' fluye a la velocidad de fotogramas deseada.
Fluir:
[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étodo 2. Ponga el SI en un trabajador web. ¡Este es FAAAST y suave!
Igual que el método 1, pero coloca el SI en web-worker. Se ejecutará en un hilo totalmente separado, dejando la página para tratar solo con la RAF y la interfaz de usuario. Pase el conjunto de sprites de ida y vuelta como un 'objeto transferible'. Esto es muy rápido. No toma tiempo clonar o serializar, pero no es como pasar por referencia, ya que la referencia del otro lado se destruye, por lo que deberá hacer que ambos lados pasen al otro lado y solo actualizarlos cuando estén presentes, ordenar de pasar una nota de ida y vuelta con tu novia en la escuela secundaria.
Solo uno puede leer y escribir a la vez. Esto está bien siempre que verifiquen si no está indefinido para evitar un error. El RAF es RÁPIDO y lo devolverá de inmediato, luego pasará por un montón de marcos de GPU solo para verificar si ya se ha enviado de vuelta. El SI en el web-worker tendrá la matriz de sprites la mayor parte del tiempo y actualizará los datos de posición, movimiento y física, así como creará la nueva cadena de transformación y luego la devolverá a la RAF en la página.
Esta es la forma más rápida que conozco para animar elementos mediante un script. Las dos funciones se ejecutarán como dos programas separados, en dos subprocesos separados, aprovechando las CPU de varios núcleos de una manera que un solo script js no lo hace. Animación javascript multihilo.
Y lo hará sin problemas sin jank, pero a la velocidad de cuadro especificada real, con muy poca divergencia.
Resultado:
Cualquiera de estos dos métodos asegurará que su script se ejecute a la misma velocidad en cualquier PC, teléfono, tableta, etc. (dentro de las capacidades del dispositivo y el navegador, por supuesto).
requestAnimationFrame
es (como su nombre indica) solicitar un cuadro de animación solo cuando es necesario. Digamos que muestra un lienzo negro estático, debería obtener 0 fps porque no se necesita un nuevo marco. Pero si está mostrando una animación que requiere 60 fps, también debería obtenerla.rAF
simplemente permite "omitir" tramas inútiles y luego guardar la CPU.