Queste sono tutte buone idee in teoria, fino a quando non vai in profondità. Il problema è che non è possibile limitare un RAF senza de-sincronizzarlo, sconfiggendo il suo scopo per esistere. Quindi si lascia funzionare a piena velocità, e aggiornare i dati in un ciclo separato , o anche un thread separato!
Sì, l'ho detto. È possibile fare multi-threaded JavaScript nel browser!
Ci sono due metodi che conosco che funzionano estremamente bene senza jank, usando molto meno succo e creando meno calore. Il risultato netto è un accurato timing su scala umana ed efficienza della macchina.
Mi scuso se questo è un po 'prolisso, ma qui va ...
Metodo 1: aggiornamento dei dati tramite setInterval e grafica tramite RAF.
Utilizzare un setInterval separato per l'aggiornamento di valori di traduzione e rotazione, fisica, collisioni, ecc. Conservare tali valori in un oggetto per ciascun elemento animato. Assegna la stringa di trasformazione a una variabile nell'oggetto ogni setInterval 'frame'. Conservare questi oggetti in un array. Imposta l'intervallo sui fps desiderati in ms: ms = (1000 / fps). Ciò mantiene un clock costante che consente gli stessi fps su qualsiasi dispositivo, indipendentemente dalla velocità RAF. Non assegnare qui le trasformazioni agli elementi!
In un ciclo requestAnimationFrame, scorrere all'interno dell'array con un ciclo di vecchia scuola per-- non usare qui i moduli più recenti, sono lenti!
for(var i=0; i<sprite.length-1; i++){ rafUpdate(sprite[i]); }
Nella tua funzione rafUpdate, ottieni la stringa di trasformazione dall'oggetto js nell'array e il suo ID elementi. Dovresti già avere i tuoi elementi "sprite" collegati a una variabile o facilmente accessibili con altri mezzi in modo da non perdere tempo a "recuperarli" nella RAF. Mantenerli in un oggetto che prende il nome dal loro ID HTML funziona abbastanza bene. Imposta quella parte prima ancora che entri nel tuo SI o RAF.
Utilizzare RAF per aggiornare solo le trasformazioni , usa solo trasformazioni 3D (anche per 2d) e imposta css "will-change: transform;" su elementi che cambieranno. Ciò mantiene quanto più possibile sincronizzato le tue trasformazioni con la frequenza di aggiornamento nativa, attiva la GPU e indica al browser dove concentrarsi maggiormente.
Quindi dovresti avere qualcosa di simile a questo pseudocodice ...
// 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
Ciò mantiene gli aggiornamenti degli oggetti dati e trasforma le stringhe sincronizzate con la frequenza di "frame" desiderata nel SI e le assegnazioni di trasformazione effettive nel RAF sincronizzate con la frequenza di aggiornamento della GPU. Quindi gli attuali aggiornamenti grafici sono solo nel RAF, ma le modifiche ai dati e la costruzione della stringa di trasformazione sono nel SI, quindi non ci sono jankies ma il "tempo" scorre al frame rate desiderato.
Flusso:
[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]
Metodo 2. Inserisci l'IS in un web-worker. Questo è FAAAST e liscio!
Come il metodo 1, ma inserisci l'IS nel web-worker. Verrà quindi eseguito su un thread completamente separato, lasciando la pagina per occuparsi solo di RAF e UI. Passa l'array sprite avanti e indietro come "oggetto trasferibile". Questo è veloce. Non ci vuole tempo per clonare o serializzare, ma non è come passare per riferimento in quanto il riferimento dall'altra parte viene distrutto, quindi dovrai far passare entrambi i lati dall'altra parte e aggiornarli solo quando presenti, ordina di come passare una nota avanti e indietro con la tua ragazza al liceo.
Solo uno può leggere e scrivere alla volta. Questo va bene finché controllano se non è indefinito per evitare un errore. Il RAF è VELOCE e lo riavvierà immediatamente, quindi passerà attraverso un sacco di frame GPU solo controllando se è stato rispedito ancora. L'IS nel web-worker avrà l'array sprite per la maggior parte del tempo e aggiornerà i dati di posizione, movimento e fisica, oltre a creare la nuova stringa di trasformazione, quindi passarla al RAF nella pagina.
Questo è il modo più veloce che conosco per animare elementi tramite script. Le due funzioni verranno eseguite come due programmi separati, su due thread separati, sfruttando le CPU multi-core in un modo in cui un singolo script js non lo fa. Animazione javascript multi-thread.
E lo farà senza intoppi, ma al frame rate specificato, con una divergenza minima.
Risultato:
Uno di questi due metodi garantirà che lo script venga eseguito alla stessa velocità su qualsiasi PC, telefono, tablet, ecc. (Ovviamente all'interno delle funzionalità del dispositivo e del browser).
requestAnimationFrame
sia (come suggerisce il tipo di nome) di richiedere un frame di animazione solo quando è necessario. Diciamo che mostri una tela nera statica, dovresti ottenere 0 fps perché non è necessaria una nuova cornice. Ma se stai visualizzando un'animazione che richiede 60 fps, dovresti ottenere anche quella.rAF
consente solo di "saltare" i frame inutili e quindi di salvare la CPU.