如何处理/安排由javascript for循环创建的大型事件队列

时间:2012-06-20 00:01:17

标签: jquery queue jquery-animate

我正在使用jquery animate()从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);
  }
}

3 个答案:

答案 0 :(得分:2)

因为javascript是单线程的,所以如果你启动一个动画(可能会在将来很短的时间内执行setTimeout()),然后你运行了一堆其他的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:

这是另一个想法。对动画进行排序并使用最快的startTime调用动画上的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值),那么您将启动第一个,然后是第二个将开始,第三个开始,等等...而所有其他人都开始,实际的动画还没有运行,因为他们的实际显示动画的计时器被困在队列中所有动画试图得到开始。关键是尽量减少动画运行后必须完成的工作量,并尽量分散工作,这样就不会一次完成大量的工作。

答案 1 :(得分:0)

不幸的是,关于动画和JavaScript(jQuery)性能的说法很不错。在我试图找到办法做这些事情的那些年里,我已经克服了几个障碍,但最后却完全相同:you have to give more than you'll get.

以下是您可以对性能问题采取的措施,以及为什么要提供的费用超过您的收益。

什么,如何以及为什么

jQuery使用JavaScript函数setInterval更新DOM以反映对象中的更改。 (无论是位置,方面,不透明度 - 无论如何)。

该函数在jQuery中重写为step,step设置为每13毫秒触发一次。因为你动画了很多物体 - 无论大小,形状,任何东西 - 它都会超出浏览器的范围。

我们可以做些什么呢?

这里有两个选择,它们都不适合你。

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

$.fx.interval

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

作为旁注,以上内容将更改所有动画的队列计时器。如果您只需要它来处理特定动画,您可以谷歌搜索以获得快速答案。这只是令人反感的信息。

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

我希望这有帮助

答案 2 :(得分:0)

两点:

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

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

如果for循环与startTime延迟相比需要很长时间才能完成,那么一旦for循环的线程完成,许多排队的动画就可以启动 - 以及所有精心策划的微观时间刚走出窗外。

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

编辑:

基于jfriend00的想法,这里有一些东西(禁止错误)与Idea#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]);
            }
        }
    }
}

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