因为 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()
最后调用 startTime 最快的动画。这使得setTimeout()
在我们仍在初始化动画时 a will 不太可能触发。
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 值),那么您将启动第一个,然后将启动第二个,第三个开始,等等......虽然所有其他人都开始了,但实际的动画还没有运行,因为他们实际显示动画的计时器被卡在所有试图开始的动画之后的队列中。关键是尽量减少任何动画运行后必须发生的工作量,并尝试分散工作量,这样就不会一次完成大量工作。