为什么多个setTimeout()调用会导致如此多的延迟?

6
我有一个涉及JavaScript中淡入淡出和过渡的复杂动画序列。在这个包含四个元素同时变化的序列中,每个元素都使用了setTimeout
在Internet Explorer 9中测试,动画以实时速度工作(应该需要1.6秒,确切地花费了1.6秒)。任何其他浏览器都会非常卡顿,动画时间为4秒(Firefox 3和4、Chrome、Opera),而IE 8及以下版本则需要大约20秒。 为什么IE9可以如此快,而其他所有浏览器都卡住了? 我已经尝试过将元素合并为一个,以便在任何给定时间只有一个setTimeout,但不幸的是它无法承受任何干扰(例如在当前动画完成之前点击不同的链接开始新动画)。
编辑:为了回应评论,以下是代码概述:
link.onclick = function() {
    clearTimeout(colourFadeTimeout);
    colourFadeTimeout = setTimeout("colourFade(0);",25);

    clearTimeout(arrowScrollTimeout);
    arrowScrollTimeout = setTimeout("arrowScroll(0);",25);

    clearTimeout(pageFadeOutTimeout);
    pageFadeOutTimeout = setTimeout("pageFadeOut(0);",25);

    clearTimeout(pageFadeInTimeout);
    pageFadeInTimeout = setTimeout("pageFadeIn(0);",25);
}

每个函数都将渐变进度推进一帧,然后设置另一个超时时间,参数加上一,直到动画结束。
你可以在http://adamhaskell.net/cw/index.html上查看该页面(用户名:knockknock;密码:goaway)(它有声音和音乐,可以禁用,但要注意!)- 我的JavaScript非常混乱,因为我没有真正地组织好它,但是它有些注释,所以希望你能理解其总体思想。

你能详细说明一下吗?是动画本身慢,还是由于等待超时而在动画的不同部分之间存在等待时间?另外,提供一些代码会有所帮助。 - deceze
动画的四个部分是:(1)使用opacity淡出当前页面,(2)使用opacity淡入新页面,(3)使用background-color淡化页面颜色,(4)使用left滚动导航栏上的指针(是的,这非常过度,但主要是为了炫耀)。动画应以40FPS运行(超时间隔为25ms),但在较慢的浏览器中,每帧所需时间至少是正常情况下的两倍,并且会出现可见的跳跃。 - Niet the Dark Absol
发布代码可能会让你感到痛苦,因为它现在非常混乱... - Niet the Dark Absol
1
你可能在某个地方漏掉了一个跳舞的宝宝。 - Ben
1
我认为一次发生太多事情了,这与setTimeout无关。你可能想利用这个页面来推广IE9中优秀的Javascript引擎,但如果你希望其他浏览器也能够处理它,你可能需要稍微减弱一下效果。(或者以更好的方式实现它们。不过我不会去查看你混乱的脚本以进行优化 :)) - deceze
显示剩余6条评论
2个回答

13

几件事情:

  1. 你的超时时间是25毫秒。这相当于40帧每秒,通过javascript实现这样高的帧率非常困难。特别是对于涉及DOM操作可能触发重绘的事情。将超时时间增加到50或60。15帧每秒应该足够流畅,适用于您正在进行的动画类型。您不是在尝试展示视频,只是在页面上移动元素。

  2. 不要将字符串作为setTimeout()的第一个参数。特别是如果您关心性能。这将强制javascript在每个动画帧中重新编译字符串。使用函数代替。如果需要传递参数,请使用匿名函数来包装要执行的函数:

    setTimeout(function(){
        pageFadeIn(0)
    },50);
    

    这将仅在脚本加载时编译一次。

  3. 正如Ben所提到的,使用单个setTimeout来安排函数更加经济实惠。为此,使用setInterval可能会提高代码清晰度(或者可能不会,这取决于您的编码风格)。


  4. 附加答案:

    编程javascript动画的关键在于优化和平衡。可以在页面上同时动画许多东西而几乎没有减慢速度,但您需要知道如何正确做并决定舍弃哪些内容。我几年前写的一个示例即是实时策略游戏。

    我为了优化游戏所做的事情包括:

    1. 步行士兵只由两帧动画组成,我只是在两个图像之间切换。但效果非常令人信服。您不需要完美的动画,只需要看起来令人信服的动画。

    2. 我对所有内容都使用单个setInterval。在CPU方面更便宜且更容易管理。只需确定基本帧速率,然后在不同的时间安排不同的动画开始即可。


再次谢谢您的答复。我之前不知道可以将一个函数传递给setTimeout,这将在以后让事情变得更容易!如果它起作用的话,我一定会告诉您的。另外,我会将帧率减半(我确保所有动画都有一个是2的幂次方的帧数,这样可以实现简单的缩放,并保持数字的完美精度,除非我搞错了)。 - Niet the Dark Absol
好的,一切都正常工作了。Firefox 仍然比较慢,但我认为这只是引擎本身在 IE9 中更好,因为速度不像以前那样慢了(现在只比应该慢的时间多了不到半秒)。非常感谢您的帮助 - 我不知道您可以将函数传递给 setTimeout。 - Niet the Dark Absol
那是一个非常好的答案(和示例)。 - Ben
4
@slebetman,您的回答中的链接已经失效。如果您还有源文件,请重新发布链接。 - Gabriel Moretti
我不认为40fps对于JS来说是非常高的帧率 - 它可以在60fps下完成非常复杂的任务(我经常使用canvas完成这项工作)。但我同意,在这个速率下进行DOM操作可能会有问题。 - UpTheCreek
1
@UpTheCreek:在2010年,40fps是非常高的帧率 - 它会让IE6变得非常缓慢。是的,在2010年我们仍然需要支持IE6和IE7。 - slebetman

3

嗯,这是很多 JavaScript 代码(尽管有“四倍的神奇”:)

你正在触发很多 setTimeout 序列,我不确定 JS 引擎对此有多少优化... 特别是 IE <= 8

好吧,也许只是一个粗略的建议... 你可以编写一个小的计时引擎。

维护一个全局对象,用于存储当前正在运行的定时事件及其要运行的函数和延迟...

然后有一个单一的 setTimeout 处理程序,检查该全局对象,并在每次迭代中减少延迟,并在延迟变为 <0 时调用该函数。

你的全局事件看起来会像这样:

var events = {

        fade1 : {
            fn : func_name,
            delay : 25,
            params : {}
        }

        fadeArrow : {
            fn : func_name,
            delay : 500,
            params : {}
        }

        slideArrow : {
            fn : func_name,
            delay : 500,
            params : {
                arrow:some_value
            }
        }

    }

然后创建一个函数以每个间隔(例如10或20毫秒)循环这些事件,减少延迟直到它们完成并使用参数作为该函数的参数来触发该函数(请参阅Function.call)。
一旦完成,请再次使用相同的延迟触发setTimeout。
要取消事件,请从事件列表中取消设置属性。
构建一些方法以添加/删除排队事件、更新参数等。
这将把所有内容都缩减为只有一个超时处理程序。
(仅供参考)

我明天尝试一下(因为这里现在是凌晨3点),然后告诉你结果。 - Niet the Dark Absol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接