JavaScript基于时候的动画算法

作者:戴嘉华

转载请说明出处,保存原文链接和作者信息

目次

  • 媒介
  • 基于帧的动画算法(Frame-based)
  • 基于时刻的动画算法(Time-based)
  • 改进基于时刻的动画算法
  • 总结

媒介

前段时刻无聊或有聊地做了几个挪动端的HTML5游戏。放在差别的挪动端平台上举行测试后有了诡异的发明,有些手机的动画会“快”一点,有些手机的动画会“慢”一点,有些慢得还不是一两点。

经由过程查找材料发明,基于帧的算法(Frame-based)来完成动画会致使差别帧率的平台体验不一致,而基于时刻(Time-based)的动画算法能够很好地改进这类状况,让差别帧率的状况下都能到达较为一致的速率上的体验。

本文引见的就是基于帧动画算法和基于时刻动画算法的差别,以及对基于时刻算法的改进。

基于帧的动画算法(Frame-based)

置信做过前端的人对运用JavaScript完成动画的道理都很熟习。如今让你完成一个让一个div从左到右往返挪动的JS代码,你能够嗖嗖就写出来了:

    function moveDiv(div, fps) {
        var left = 0;
        var param = 1;

        function loop () {
            update();
            draw();
        }

        function update() {
            left += param * 2;
            if (left > 300) {
                left = 300;
                param = -1;
            } else if (left < 0) {
                left = 0;
                param = 1;
            }
        }

        function draw() {
            div.style.left = left + "px";
        }

        setInterval(loop, 1000 / fps);
    }
    moveDiv(document.getElementById("div1"), 60);

结果以下:

http://jsfiddle.net/livoras/4taf9hhs/embedded/result,js,html,css/

看看代码,我们让一个div在0 ~ 300px区间内摆布往返挪动。update盘算更新描写div的位置,draw从新描写页面上的div。为了轻易起见,这里直接运用setInterval作为定时器,现实状况下能够采纳你喜好的setTimeout或许requestAnimationFrame。这里设置每秒钟到更新60次,60fps是人尽皆知的比较适合做动画的帧率。

地球人都知道,JavaScript中的定时器是不正确的。由于JavaScript运转时须要消耗时刻,而JavaScript又是单线程的,所以如果一个定时器如果比较耗时的话,是会壅塞下一个定时器的实行。所以纵然你这里设置了1000 / 60每秒60帧的帧率,在差别的浏览器平台的差别也会致使现实上你的没有60fps的帧率。

所以上面代码在一个手机上实行的时刻能够有60fps的帧率,在别的一个手机上能够就只要30fps,愈甚能够只要10fps。

我们模仿一下这类状况会有什么结果发作:

http://jsfiddle.net/livoras/Lcv1jm53/embedded/result,js,html,css/

这完整不对大头!

能够看到三个方块挪动速率基础不在同一个channel上。设想一下一个超等马里奥游戏在10fps的状况会怎样?按腾跃一下,你会看到马里奥以一种太空遨游的姿势在空中抛弧线。

致使这类状况的缘由很简朴,由于我们盘算和绘制每一个div位置的时刻是在每帧更新,每帧挪动2px。在60fps的状况下,我们1秒钟会实行60帧,所以小块每秒钟会挪动60 * 2 = 120px;如果是30fps,小块每秒就挪动30 * 2 = 60px,以此类推10fps就是每秒挪动20px。

三个小块在单元时刻内挪动的间隔不一样!

如果你如今要做一个超等马里奥的游戏,怎样做到能够在差别帧率的状况下让马里奥看起来照样那末敏捷且帅气?

处理方案很显著。虽然差别的浏览器平台上的运转差别能够会致使帧率的不一致,然则有一样东西是在任何平台上都一致的,那就是时刻。所以我们能够改进我们的算法,不是以帧为基准来更新方块的位置,而是以时刻为单元更新。也就是说,我们之前是px/frame,如今换成px/ms

这就是接下来要说的基于时刻(Time-based)的动画算法。

基于时刻的动画算法(Time-based)

实在思绪和完成都很简朴。我们盘算每一帧离上一帧过去了若干时刻,然后依据过去的时刻来更新方块的位置。

比方,上面的方块应当每秒钟挪动120px,每毫秒挪动120 / 1000 = 0.12像素(12px/ms)。如果上一帧方块的位置在left为10px的位置,到了这一帧的时刻,假定相对于上一帧来讲时刻过去了200ms,那在时刻上来讲在这一帧方块应当挪动200ms * 0.12px/ms = 240px。终究位置应当为10 + 240 = 250px。实在就是left = left + detalTime * speed。代码以下:

    function moveDivTimeBased(div, fps) {
        var left = 0;
        var current = +new Date;
        var previous = +new Date;
        var param = 1;

        function loop() {
            var current = +new Date;
            var dt = current - previous; // 盘算时刻差
            previous = current;
            update(dt);
            draw()
        }

        function update(dt) {
            left += param * (dt * 0.12); // 依据时刻差更新位置
            if (left > 300) {
                left = 300;
                param = -1;
            } else if (left < 0) {
                left = 0;
                param = 1;
            }
        }        

        function draw() {
            div.style.left = left + "px";
        }

        setInterval(loop, 1000 / fps);
    }

看看结果怎样:

http://jsfiddle.net/livoras/8da1nssL/embedded/result,js,html,css/

看起来比上面的好多了,30fps和10fps彷佛能委曲遇上60fps的步调。然则时刻久了会发明30fps和10fps愈来愈落后于60fps。(发起先革新再看看结果会越发显著)

这是由于每次小方块遇到边沿的时刻,都邑丧失掉一部分时刻,而且帧率越低的丧失越大。看看我们上面的update函数:

      function update(dt) {
          left += param * (dt * 0.12); // 依据时刻差更新位置
          if (left > 300) {
              left = 300;
              param = -1;
          } else if (left < 0) {
              left = 0;
              param = 1;
          }
      }

如果我们如今方块的位置在left为290px的位置,这一帧传入的dt为100ms,那末我们left为290 + 100 * 0.12 = 302,然则302大于300,所以left会被设置为300。那末原本用来挪动2px的时刻就会白白被“扬弃”掉。dt越大,糟蹋得越多,所以30fps和10fps会比60fps愈来愈慢。

为了处理这个题目,我们对已有的算法举行改进。

改进基于时刻的动画算法

处理思绪以下:不一次算整块的时刻(dt)挪动的间隔,而是把dt分红牢固的时刻片,经由过程屡次update牢固的时刻片来盘算dt时刻后应当到什么位置。

比较笼统,我们直接看代码:

    function moveDivTimeBasedImprove(div, fps) {
        var left = 0;
        var current = +new Date;
        var previous = +new Date;
        var dt = 1000 / 60;
        var acc = 0;
        var param = 1;

        function loop() {
            var current = +new Date;
            var passed = current - previous;
            previous = current;
            acc += passed; // 积聚过去的时刻
            while(acc >= dt) { // 当时刻大于我们的牢固的时刻片的时刻能够举行更新
                update(dt); // 分片更新时刻
                acc -= dt;
            }
            draw();
        }

        // update 和 draw 函数稳定
        setInterval(loop, 1000 / fps);
    }

我们先肯定一个牢固更新的时刻片,如牢固为60fps时一帧的时刻:1000 / 60 = 0.167ms。然后积聚过去的时刻,然后依据牢固时刻片分片举行更新。也就说,纵然这一帧和上一帧相差过去了100ms,我也会把这100ms分红很多个0.167ms来实行update函数。如许做有两个优点:

  1. 牢固的时刻片充足小,更新的时刻能够削减边沿丧失的时刻。
  2. 差别帧率,不论你是60,30,照样10fps,也是依据牢固时刻片来实行update函数,所以纵然有丧失,差别帧率之间的丧失是一样的。那末我们三个方块就能够到达同步挪动的结果的了!

看上面的代码,update和draw函数坚持稳定,而loop函数中,对过去的时刻举行了累加,当时刻凌驾牢固的片就能够实行update。while轮回能够保证更新直到把积聚的时刻都更新完。

对时刻举行积聚,然后分牢固片更新。这类体式格局另有一个非常大的优点,如果你的帧率凌驾了60fps,如到达100fps或许200fps,这时刻passed会小于0.167ms,时刻就会被积聚,积聚大于0.167才会实行更新。堡垒的结果就是:不论你的帧率是高照样低,挪动速率都能够和60fps状况下的速率同步。

看看末了的结果:

http://jsfiddle.net/livoras/25nut92z/embedded/result,js,html,css/

照样蛮不错的。

总结

基于帧的动画算法会在帧率差别的状况下致使动画体验有较大的差别,一切动画都应当基于时刻举行实行。而基于时刻的动画算法要注意边沿时刻的丧失,最好采用积聚时刻,然后分牢固片更新动画的体式格局。

References

    原文作者:戴嘉华
    原文地址: https://segmentfault.com/a/1190000002416071
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞