因为 javascript 是单线程的,所以如果你启动一个动画(它可能会在未来一段时间内执行 setTimeout()),然后你运行一堆其他的 javascript,这些 javascript 比第一次要长得多setTimeout(),然后 setTimeout() 将无法触发/运行,直到您的其他 javascript 执行完毕。当它最终运行时,它会意识到圣牛,它的方式,远远落后于计划,任何体面的补间算法都会尝试回到计划并跳过一堆动画的初始部分。这听起来像你所描述的所见。
解决此问题的唯一方法是避免在启动动画后运行代码的任何重要时间,因为正是运行的代码使动画落后于计划。如果您愿意仅针对此问题优化从开始到结束的动画过程,并且可以更改您自己的动画代码,则可以运行并初始化每个动画,以便预先计算所有初始状态,但没有实际动画的开始。然后,一旦运行所有代码来设置所有动画,您将运行一个非常快速的循环来启动它们全部运行。这将最小化第一个动画开始后的代码运行时间。
我现在真的不知道是什么花费了所有的初始化时间,也没有对时间的真正去向进行基准测试,但是您可以通过预先计算所有初始动画参数,将它们全部存储到一个数组,然后在您完成所有这些预先计算后启动所有动画:
想法 #1:
function runAnimation() {
// create node list of paths
var allPaths = document.getElementById('svgcontainer').getElementsByTagName('path');
// define the animate function
var doAnim = function(currentPath, dur, begin) {
setTimeout(function(){
$(currentPath).animate({'stroke-dashoffset': 0}, dur);
}, begin);
};
var anims = [];
// iterate through the nodelist
for (var i=0, len = allPaths.length; i<len; i++) {
var pathAnim = allPaths[i].firstChild;
startTime = parseFloat(pathAnim.getAttribute('begin'));
pathDuration = parseFloat(pathAnim.getAttribute('dur'));
// change times from seconds to milliseconds
startTime = startTime * 1000;
pathDuration = pathDuration * 1000;
// accumulate animation parameters, but don't start animation yet
anims.push([allPaths[i], pathDuration, startTime]);
}
// now start all animations as fast as possible
for (var i = 0, len = anims.length; i < len; i++) {
doAnim.apply(this, anims[i]);
}
}
老实说,这个代码更改看起来不会节省很多(这段代码中没有发生太多可能需要很多时间的事情),但如果循环的大小很大,它可能是一个有意义的改进。
想法 #2:
这是另一个想法。对动画进行排序,并在最后开始时间最快的动画上调用doAnim()。这使得 setTimeout() 不太可能在我们仍在初始化动画时触发。
function runAnimation() {
// create node list of paths
var allPaths = document.getElementById('svgcontainer').getElementsByTagName('path');
// define the animate function
var doAnim = function(currentPath, dur, begin) {
setTimeout(function(){
$(currentPath).animate({'stroke-dashoffset': 0}, dur);
}, begin);
};
var anims = [];
// iterate through the nodelist
for (var i=0, len = allPaths.length; i<len; i++) {
var pathAnim = allPaths[i].firstChild;
startTime = parseFloat(pathAnim.getAttribute('begin'));
pathDuration = parseFloat(pathAnim.getAttribute('dur'));
// change times from seconds to milliseconds
startTime = startTime * 1000;
pathDuration = pathDuration * 1000;
// accumulate animation parameters, but don't start animation yet
anims.push([allPaths[i], pathDuration, startTime]);
}
// sort array so that smallest startTime values are last
anims.sort(function(a, b) {
return(b[2] - a[2]);
});
// Now start all animations as fast as possible
// Because the array is sorted, it will start the longer setTimeout()
// calls first and lessen the chance that the short ones will not get
// get to fire when they want to
for (var i = 0, len = anims.length; i < len; i++) {
doAnim.apply(this, anims[i]);
}
}
除此之外,任何其他修复都可能必须在 .animate() 代码本身中,因为它有自己的设置时间,所以如果太多对象都试图同时启动它们的动画,那不会保持平稳。
关于 SetTimeout() 如何与事件队列一起工作的注意事项:
以下是setTimeout() 与 javascript 事件队列一起工作的方式。系统计时器被安排在未来的某个时间。当达到该时间时,该计时器的事件将被放入 javascript 事件队列中。如果此时 javascript 引擎处于空闲状态,则立即执行相应的回调。如果 javascript 引擎此时正在执行其他操作,那么该计时器甚至只是停留在事件队列中。当当前正在执行的 javascript 线程完成其执行时,它会检查事件队列中是否还有更多事件。如果有一个事件在等待,它会从队列中拉出最旧的一个并开始执行它。该过程一直重复,直到一个执行的 javascript 线程完成并且队列中没有更多事件。虽然 javascript 事件一次只能执行一个,但新事件可以在计时器事件触发时实时添加到队列中(或发生鼠标点击或按键等...)。
如您所见,由于 javascript 是单线程的,如果同时有很多事情要做(比如要启动很多动画),那么实际上只有其中一件事情会准时启动其他的会延迟一些。
在您的特定代码中,如果您的代码试图同时启动一大堆动画(例如,所有动画都具有相同或非常接近的 startTime 值),那么您将启动第一个,然后是第二个将开始,第三个开始,等等......虽然所有其他人都开始了,但实际的动画还没有运行,因为他们实际显示动画的计时器被卡在所有试图获取的动画之后的队列中开始了。关键是要尽量减少任何动画运行后必须发生的工作量,并尝试分散工作量,以免一次性完成大量工作。