【问题标题】:JavaScript Duration of animation isn't exactJavaScript 动画的持续时间不准确
【发布时间】:2012-06-14 23:01:41
【问题描述】:

首先我想提两件事,
一:我的代码并不完美(尤其是 eval 部分) - 但我想为自己尝试一些东西,看看能不能复制jQuery动画功能,所以请原谅我的“坏”做法,也请不要建议我用jQuery,我想试验一下。
二: 这段代码还没有完成,我只是想弄清楚是什么让它工作得不好。

所以动画运行了大约 12 秒,而我输入的持续时间参数是 15 秒,我做错了什么?

function animate(elem, attr, duration){
  if(attr.constructor === Object){//check for object literal
    var i = 0;
    var cssProp = [];
    var cssValue = [];
    for(key in attr) {
          cssProp[i] =  key;
          cssValue[i] = attr[key];
    }
    var fps = (1000 / 60);
    var t = setInterval(function(){
      for(var j=0;j<cssProp.length;j++){
        if(document.getElementById(elem).style[cssProp[j]].length == 0){
          //asign basic value in css if the object dosn't have one.
         document.getElementById(elem).style[cssProp[j]]= 0;
        }
        var c = document.getElementById(elem).style[cssProp[j]];
        //console.log(str +" | "+c+"|"+cssValue[j]);
        if(c > cssValue[j]){
            document.getElementById(elem).style[cssProp[j]] -= 1/((duration/fps)*(c-cssValue[j]));
        }else if(c < cssValue[j]){
            document.getElementById(elem).style[cssProp[j]] += 1/((duration/fps)*(c-cssValue[j]));
        }else if(c == cssValue[j]){
            window.clearInterval(t);
        }
      }
    },fps);
  }
}
  animate('hello',{opacity:0},15000);

html:

  <p id="hello" style="opacity:1;">Hello World</p>

注意:我猜是有问题

(持续时间/fps)*(c-cssValue[j])

setInterval(fps 变量)的部分或/和间隔。


提前致谢

【问题讨论】:

  • 请先删除 eval 部分。使用 var style = document.getElementById(elem).style[cssProp[j]]; 而不是那个丑陋的 str 建筑物。
  • 首先,通过这么多eval 进行调试将非常痛苦。首先清理它...提醒一下,eval("foo("+str+")")foo(str) 相同。而eval("obj."+ propName)obj[propName] 相同。这两条规则应该允许删除那里的几乎所有 eval。
  • eval 用 a 而不是 i 是邪恶的
  • 我已经编辑了它,现在没有 evals...有人知道为什么会这样吗?
  • 什么是k?它没有在这个 sn-p 中的任何地方声明或设置。考虑到补间计算的一​​部分似乎很重要。

标签: javascript setinterval duration


【解决方案1】:

我不会尝试重构它并弄清楚它,因为它非常不稳定。那就是说……一些事情。

不要依赖你正在制作动画的值来让你知道动画进度

一般来说,您的方法是不合理的。你最好自己跟踪进度。此外,由于您的方法,您的数学似乎过于努力,应该更简单。

这样想:你的动画是在时间过去后完成的,而不是当动画值似乎表明它处于最终位置时。

不要递增,设置

浮点数学是不精确的,像这样的重复加法累积也会累积浮点错误。并且为您创建一些变量来跟踪进度,这更具可读性,您可以在计算中使用这些变量。

animatedValue += changeOnThisFrame // BAD!
animatedValue = valueOnThisFrame   // GOOD!

不要做积极/消极的条件舞

原来10 + 1010 - (-10) 真的是一回事。这意味着您始终可以添加值,但变化率可以是负值或正值,并且值将在适当的方向上生成动画。

超时和间隔不准确

原来setTimeout(fn, 50) 实际上意味着安排 fn 至少在 50 毫秒后被调用。在这 50 毫秒之后执行的下一个 JS 运行循环将运行该函数,因此您不能完全准确地依赖它。

也就是说,它通常在几毫秒内。但是 60fps 大约是 16ms 的帧,并且该计时器实际上可能会在 16-22ms 的可变时间内触发。因此,当您根据帧速率进行计算时,它与实际经过的时间完全不匹配。

重构复杂的数学

在这里解构这条线会很困难。

document.getElementById(elem).style[cssProp[j]] -= 1/((duration/fps)*(c-cssValue[j]));

为什么要更复杂地分解它,以便您可以轻松理解这里发生了什么。单独重构这一行,我可能会这样做:

var style = document.getElementById(elem).style;
var changeThisFrame = duration/fps;
var someOddCalculatedValue = c-cssValue[j];
style[cssProp[j]] -= 1 / (changeThisFrame * someOddCalculatedValue);

这样做可以让您更清楚地了解数学中的每个表达式的含义和用途。而且因为你没有在这里做,我很难想知道为什么c-cssValue[j] 会在里面以及它代表什么。

简单示例

这比你拥有的能力要差,但它显示了你应该采取的方法。它使用动画开始时间来创建完美的值,这取决于动画应该有多完整、它从哪里开始以及它要去哪里。它不使用当前动画值来确定任何内容,并保证运行动画的完整长度。

var anim = function(elem, duration) {

    // save when we started for calculating progress
    var startedAt = Date.now();

    // set animation bounds
    var startValue = 10;
    var endValue   = 200;

    // figure out how much change we have over the whole animation
    var delta = endValue - startValue;

    // Animation function, to run at 60 fps.
    var t = setInterval(function(){

        // How far are we into the animation, on a scale of 0 to 1.
        var progress = (Date.now() - startedAt) / duration;

        // If we passed 1, the animation is over so clean up.
        if (progress > 1) {
            alert('DONE! Elapsed: ' + (Date.now() - startedAt) + 'ms');
            clearInterval(t);
        }

        // Set the real value.
        elem.style.top = startValue + (progress * delta) + "px";

    }, 1000 / 60);
};

anim(document.getElementById('foo'), 5000);
​

JSFiddle:http://jsfiddle.net/DSRst/

【讨论】:

  • 关于“浮点错误”,它们将是微不足道的,因为它们发生在第 15 位有效数字左右。无卡舍入更有可能导致动画无法顺利进行或无法在正确的时间间隔内进行。
  • 我遵循了您的解决方案,但它只出现了大约 7 毫秒的错误。谢谢!
  • @RobG 是的,这可能不是一个很大的因素,但是当你可以有一个更准确的数字时,为什么还要有一个不太准确的数字呢?
【解决方案2】:

您不能使用setInterval 来获得准确的总计时。因为 JS 是单线程的,多个事物在一个线程上竞争周期,所以不能保证下一次间隔调用会完全准时,或者 N 个间隔会消耗确切的持续时间。

相反,几乎所有动画例程都会获取当前时间并使用系统时钟来测量总持续时间的时间。一般的算法是获取开始时间,计算出期望的结束时间(开始时间+持续时间)。然后,正如您所做的那样,计算预期的步长值和迭代次数。然后,在每一步中,您重新计算剩余的剩余时间和剩余的步长值。通过这种方式,您可以确保动画始终准确(或几乎准确)按时完成,并且始终准确到达最终位置。如果动画落后于理想轨迹,那么它将自我纠正并在剩余的步骤中稍微移动更多。如果它因任何原因(舍入误差等)领先,它会回拨步长,同样准时到达最终位置。

您可能还需要知道浏览器并不总是支持非常小的计时量。每个浏览器都有某种允许计时器操作的最短时间。这是an article 的最低计时器级别。

这是关于补间的an article(不断重新计算步骤以准确适应持续时间的过程)。

我还建议您查看一些库(jQuery、YUI 或您找到的任何其他库)中用于制作动画的代码,因为它们都可以向您展示这是如何以非常通用的方式完成的,包括补间, 缓动函数等...

【讨论】:

  • 感谢您的解决方案,你们都有相同的 (~) 解决方案,但 Alex 的解决方案更全面,无论如何谢谢!
  • +1 对于使用 setTimeout 并在每次迭代中将时间更正到下一步非常重要。可以包含最小时钟步长的测试(在某些浏览器中为 15 毫秒左右),因此可以允许。
猜你喜欢
  • 2016-02-28
  • 2018-06-15
  • 1970-01-01
  • 2014-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多