这些都是理论上的好主意,直到你深入为止。 问题是你不能在不去同步的情况下限制 RAF,破坏它的存在目的。 所以你让它全速运行,并在一个单独的循环甚至一个单独的线程中更新你的数据!
是的,我说过。您可以在浏览器中执行多线程 JavaScript!
我知道有两种方法在没有卡顿的情况下效果非常好,使用更少的果汁并产生更少的热量。准确的人工计时和机器效率是最终结果。
抱歉,如果这有点罗嗦,但这里有......
方法一:通过setInterval更新数据,通过RAF更新图形。
使用单独的 setInterval 更新平移和旋转值、物理、碰撞等。将这些值保存在每个动画元素的对象中。将转换字符串分配给每个 setInterval 'frame' 对象中的一个变量。将这些对象保存在一个数组中。以毫秒为单位将间隔设置为所需的 fps:ms=(1000/fps)。这可以保持稳定的时钟,在任何设备上都允许相同的 fps,无论 RAF 速度如何。 不要将变换分配给这里的元素!
在 requestAnimationFrame 循环中,使用老式的 for 循环遍历您的数组——不要在这里使用较新的形式,它们很慢!
for(var i=0; i<sprite.length-1; i++){ rafUpdate(sprite[i]); }
在您的 rafUpdate 函数中,从数组中的 js 对象获取转换字符串及其元素 id。您应该已经将您的“精灵”元素附加到变量或通过其他方式轻松访问,这样您就不会浪费时间在 RAF 中“获取”它们。将它们保存在以它们的 html id 命名的对象中效果很好。在它进入您的 SI 或 RAF 之前设置该部分。
仅使用 RAF 更新您的变换,仅使用 3D 变换(即使是 2d),并设置 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 中所需的“帧”速率,并且 RAF 中的实际变换分配同步到 GPU 刷新率。因此,实际的图形更新仅在 RAF 中,但对数据的更改和构建转换字符串在 SI 中,因此没有 jankies,而是“时间”以所需的帧速率流动。
流动:
[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。将精灵数组作为“可转移对象”来回传递。这是 buko 快。克隆或序列化不需要时间,但它不像通过引用传递,因为来自另一端的引用被破坏了,所以你需要让双方都传递到另一端,并且只在存在时更新它们,排序就像在高中时和你的女朋友来回传递一张纸条。
一次只有一个人可以读写。这很好,只要他们检查它是否未定义以避免错误。RAF 速度很快,会立即将其踢回,然后通过一堆 GPU 帧检查它是否已被发回。web-worker 中的 SI 将大部分时间拥有 sprite 数组,并将更新位置、运动和物理数据,以及创建新的转换字符串,然后将其传递回页面中的 RAF。
这是我所知道的通过脚本为元素设置动画的最快方式。这两个函数将作为两个单独的程序在两个单独的线程上运行,以单个 js 脚本所不具备的方式利用多核 CPU。多线程javascript动画。
并且它会在没有卡顿的情况下顺利进行,但在实际指定的帧速率下,几乎没有分歧。
结果:
这两种方法中的任何一种都可以确保您的脚本在任何 PC、手机、平板电脑等上以相同的速度运行(当然,在设备和浏览器的能力范围内)。