0

我正在使用 jqueryanimate()从 svg 标记中提取值,并创建一个动画。(IE不支持SMIL,需要用脚本来完成。)

这是问题所在:动画有数千个元素,每个元素都有自己的开始时间,因此for我用来迭代每个元素的循环会消耗几秒钟,在较慢的机器/浏览器上甚至更多。所以感觉就像在事件队列实际开始时,运行时已经运行了一段时间,并且已经过去了几秒钟,所以动画的最开始就提前了。动画从时间=零开始。动画的开头有时会被剪掉。

不同的浏览器似乎对这个问题的处理方式不同,而且它似乎也取决于处理器的状态,所以我很难系统地弄清楚发生了什么。)这也是我第一次涉足动画和非常长的运行时间,所以我确定我做错了什么:)

所以我的具体问题是如何避免这种行为。但更一般地说,事件队列的计时器是在运行时开始还是结束时开始?这究竟是如何工作的?

以下是大部分相关代码:

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);
  };

  // iterate through the nodelist
  for (var i=0; i<allPaths.length; 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;

    doAnim(allPaths[i], pathDuration, startTime);
  }
}
4

3 回答 3

2

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

于 2012-06-20T01:00:55.000 回答
0

两点:

  • Javascript是单线程的;
  • 每个 setTimeout 都会触发一个新线程。

将这两个事实加在一起,您可以看到您的for循环保证在任何动画开始运行之前完成。

如果for与延迟相比,循环需要很长时间才能完成,startTime那么一旦 for 循环的线程完成,许多排队的动画就可以启动了——所有精心计划的微定时都刚刚结束。

希望这可以为您描述的行为提供一些见解。

编辑:

基于 jfriend00 的想法,这里有一些东西(除了错误)将实现与想法 #2 相同但避免昂贵排序的需要。

function runAnimation() {
    var allPaths = document.getElementById('svgcontainer').getElementsByTagName('path'),
        pathAnim, startTime, pathDuration, anims = [],
        i, j, arr;

    function doAnim(i, dur, begin) {
        setTimeout(function(){
            $(allPaths[i]).animate({'stroke-dashoffset': 0}, dur);
        }, begin);
    }

    // iterate through the nodelist
    for (i = 0, len = allPaths.length; i<len; i++) {
        pathAnim = allPaths[i].firstChild;
        startTime = pathAnim.getAttribute('begin');
        pathDuration = pathAnim.getAttribute('dur');
        // Accumulate animation parameters in such a way that 
        // we avoid the need for an expensive array sort later
        if(!anims[startTime]) {
            anims[startTime] = [];//Allow for multiple events per startTime
        }
        anims[startTime].push([i, pathDuration*1000, startTime*1000]);
    }

    // At this point we have a sparse array indexed by startTime and containing arrays of animation data.
    // To iterate over a sparse array efficiently, we have to do something slightly tricky
    // ref: http://hexmen.com/blog/2006/12/iterating-over-sparse-arrays/
    for (arr in anims) {
        if (String(arr >>> 0) == arr && arr >>> 0 != 0xffffffff) {
            for(j = 0, len = arr.length; j < len; j++) {
                doAnim.apply(this, arr[j]);
            }
        }
    }
}

不要试图理解>>>稀疏迭代器中的内容。我自己并不完全理解;我只知道它有效;-)。但是,我之前没有在大容量、时间紧迫的环境中使用它,所以看看它是否足够快会很有趣。

于 2012-06-20T02:54:45.580 回答
0

不幸的是,关于动画和 JavaScript(jQuery) 的性能已经说过的话是非常正确的。在我多年来试图找到一种方法来做这些事情的过程中,我克服了一些障碍,但最终都是一样的:you have to give more than you'll get.

以下是您可以针对绩效问题采取的措施,以及为什么您付出的比得到的多。

什么、如何和为什么

jQuery 使用 JavaScript 函数setInterval更新 DOM 以反映对象的变化。(无论是位置、方面、不透明度——随便)。

该函数在 jQuery 中重写为 step,并且 step 设置为每 13 毫秒触发一次。因为您要为如此多的对象设置动画——无论大小、形状、任何东西——它都会使浏览器超负荷运行。

那我们能做些什么呢?

您在这里有两个选择,它们都不是您的优雅解决方案。

您也可以更改触发动画的时间间隔。在执行此操作时,您必须考虑您拥有的数千个元素,并确定最适合您的性能与美学比率的刷新率。

$.fx.interval

但是,您应该注意,某些浏览器对此的解释不同。还有几个插件旨在进一步优化它,并且有些成功。不过,我不提出建议,所以你必须用谷歌搜索才能找到。

作为旁注,上面将更改所有动画的队列计时器。如果您只需要它处理特定的动画,您可以谷歌搜索以获得快速答案。这只是客观地提供信息。

或者,您可以在某些位置拍摄元素的快照,并将它们制作成更大的图像,您可以根据需要进行操作和交换。如果您使用爆炸或其他方式,这可能对您不起作用,但通常您可以使用此解决方案来获得预期的效果。

我希望它有所帮助

于 2012-06-20T00:38:21.643 回答