Todas essas são boas idéias em teoria, até você se aprofundar. O problema é que você não pode estrangular um RAF sem dessincronizá-lo, derrotando seu objetivo de existir. Assim, você o deixa rodar em velocidade máxima e atualiza seus dados em um loop separado , ou mesmo em um thread separado!
Sim, eu disse. Você pode fazer JavaScript multiencadeado no navegador!
Sei que existem dois métodos que funcionam extremamente bem sem brincadeiras, usando muito menos suco e criando menos calor. O tempo preciso em escala humana e a eficiência da máquina são o resultado líquido.
Desculpas se isso é um pouco prolixo, mas aqui vai ...
Método 1: Atualize dados via setInterval e gráficos via RAF.
Use um setInterval separado para atualizar os valores de conversão e rotação, física, colisões etc. Mantenha esses valores em um objeto para cada elemento animado. Atribua a sequência de transformação a uma variável no objeto cada 'frame' setInterval. Mantenha esses objetos em uma matriz. Defina seu intervalo para os fps desejados em ms: ms = (1000 / fps). Isso mantém um relógio constante que permite os mesmos fps em qualquer dispositivo, independentemente da velocidade RAF. Não atribua as transformações aos elementos aqui!
Em um loop requestAnimationFrame, itere através de sua matriz com um loop for old-school - não use os formulários mais recentes aqui, eles são lentos!
for(var i=0; i<sprite.length-1; i++){ rafUpdate(sprite[i]); }
Na sua função rafUpdate, obtenha a string de transformação do seu objeto js na matriz e o ID dos seus elementos. Você já deve ter seus elementos de 'sprite' anexados a uma variável ou facilmente acessíveis por outros meios, para não perder tempo 'obtendo-os' na RAF. Mantê-los em um objeto com o nome de seus IDs html funciona muito bem. Configure essa peça antes mesmo de entrar no seu SI ou RAF.
Use o RAF para atualizar apenas suas transformações , use apenas transformações 3D (mesmo para 2d) e defina o css "will-change: transform;" em elementos que mudarão. Isso mantém suas transformações sincronizadas com a taxa de atualização nativa o máximo possível, ativa a GPU e informa ao navegador onde se concentrar mais.
Então você deve ter 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
Isso mantém suas atualizações nos objetos de dados e as seqüências de transformação sincronizadas com a taxa de 'quadro' desejada no SI, e as atribuições de transformação reais no RAF sincronizadas com a taxa de atualização da GPU. Portanto, as atualizações gráficas reais estão apenas no RAF, mas as alterações nos dados e a construção da cadeia de transformação estão no SI, portanto, não há bobagens, mas o 'tempo' flui na taxa de quadros desejada.
Fluxo:
[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. Coloque o SI em um trabalhador da Web. Este é FAAAST e suave!
Igual ao método 1, mas coloque o SI no web-worker. Ele será executado em um segmento totalmente separado, deixando a página para lidar apenas com o RAF e a interface do usuário. Passe a matriz de sprites para frente e para trás como um 'objeto transferível'. Isso é buko rápido. Não leva tempo para clonar ou serializar, mas não é como passar por referência, pois a referência do outro lado é destruída; portanto, você precisará que os dois lados passem para o outro lado e os atualize apenas quando presentes, classifique de passar uma nota para frente e para trás com sua namorada no ensino médio.
Somente um pode ler e escrever de cada vez. Isso é bom desde que verifique se não está indefinido para evitar um erro. O RAF é RÁPIDO e o retrocede imediatamente, passando por vários quadros de GPU, verificando se já foi enviado de volta. O SI no trabalhador da Web terá o conjunto de sprites na maior parte do tempo e atualizará os dados posicionais, de movimento e físicos, além de criar a nova sequência de transformação, depois passará para a RAF na página.
Essa é a maneira mais rápida que conheço de animar elementos via script. As duas funções serão executadas como dois programas separados, em dois threads separados, aproveitando as CPUs de vários núcleos de uma maneira que um único script js não. Animação em javascript multiencadeada.
E será feito sem problemas, mas na taxa de quadros especificada real, com muito pouca divergência.
Resultado:
Qualquer um desses dois métodos garantirá que o script seja executado na mesma velocidade em qualquer PC, telefone, tablet etc. (dentro dos recursos do dispositivo e do navegador, é claro).
requestAnimationFrame
é (como o nome sugere) solicitar um quadro de animação somente quando necessário. Digamos que você mostre uma tela preta estática, você deve obter 0 qps porque nenhum novo quadro é necessário. Mas se você estiver exibindo uma animação que requer 60fps, você também deve obtê-la.rAF
permite apenas "pular" quadros inúteis e salvar a CPU.