这些理论上都是好主意,但实际操作时会遇到问题。问题在于你无法对 requestAnimationFrame(RAF)进行节流控制,否则它就会失去作用。 因此,您需要让它以全速运行,并在单独的循环中更新数据,甚至可以使用单独的线程来做这个事情!
没错,您可以在浏览器中使用多线程 JavaScript!
我知道两种方法非常适用而且不会出现卡顿,它们消耗更少的能源并产生更少的热量。精准的人类时间尺度和机器效率是最终结果。
如果这有点啰嗦,那么接下来请看...
方法 1:使用 setInterval 更新数据,使用 RAF 更新图形。
使用一个单独的 setInterval 来更新每个动画元素的平移和旋转值、物理、碰撞等数据。将这些值保存在对象中,为每个对象分配变换字符串。将这些对象存储在数组中。将间隔设置为所需的 fps 的毫秒数:ms =(1000 / fps)。这可以保持稳定的时钟,使任何设备上的 fps 相同,而不受 RAF 速度的影响。不要在这里将变换分配给元素!
在 requestAnimationFrame 循环中,使用老式的 for 循环迭代您的数组,不要在这里使用较新的形式,因为它们速度较慢!
for(var i=0; i<sprite.length-1; i++){ rafUpdate(sprite[i]); }
在你的rafUpdate函数中,从数组中的js对象中获取变换字符串及其元素id。你应该已经将'sprite'元素附加到一个变量中或通过其他方式轻松访问,以便在RAF中不需要花费时间进行“get”。将它们保留在一个以其html id命名的对象中,这样做非常有效。在进入SI或RAF之前设置好。
只使用3D变换(即使是2D),并使用RAF仅更新您的变换,对预计会发生更改的元素设置css“will-change: transform;”。这可以尽可能地使您的变换与本机刷新率同步,激活GPU,并告诉浏览器重点关注哪些部分。
因此,您应该拥有类似于以下伪代码...
// 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
这可以确保您对数据对象和变换字符串的更新与 SI 中所需的“帧”速率同步,而实际的变换分配与 GPU 刷新速率在 RAF 中同步。因此,实际的图形更新仅在 RAF 中进行,但是数据的更改以及构建变换字符串都在 SI 中进行,因此不会出现卡顿,而“时间”以所需的帧速率流动。
流程:
[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]
方法2:将SI放在Web Worker中。这种方法非常快且流畅!与方法1相同,但将SI放入Web Worker中。它将在完全不同的线程上运行,页面只需处理RAF和UI。来回传递精灵数组作为“可转移对象”。它非常快速,不需要克隆或序列化时间,但不像通过引用传递,另一边的引用被销毁,因此您需要让双方互相传递,并仅在存在时更新它们,就像在高中时互相传递笔记一样。只有一个人可以读写。只要检查它是否未定义以避免错误,这就很好了。RAF很快,会立即将其发送回来,然后通过大量GPU帧仅检查它是否已经发送回来。 Web Worker中的SI大多数时间都会有精灵数组,并更新位置,运动和物理数据,以及创建新的变换字符串,然后将其传递回页面中的RAF。这是我知道的通过脚本动画元素的最快方法。这两个函数将作为两个单独的程序在两个单独的线程上运行,以一种单个JS脚本无法做到的方式利用多核CPU。多线程JavaScript动画。这样做不会有任何卡顿,而是以实际指定的帧速率平稳运行,发散非常小。
requestAnimationFrame
最大的优势是只在需要时请求一个动画帧(正如其名称所暗示的那样)。比如说,如果你展示了一个静态的黑色画布,你应该得到0帧每秒,因为不需要新的帧。但是,如果你正在展示一个需要60fps的动画,你也应该得到那么多帧数。rAF
允许“跳过”无用的帧,从而节省CPU。 - maxdec