国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

JavaScript基于時(shí)間的動(dòng)畫算法

TNFE / 1755人閱讀

摘要:基于時(shí)間的動(dòng)畫算法其實(shí)思路和實(shí)現(xiàn)都很簡(jiǎn)單。而基于時(shí)間的動(dòng)畫算法要注意邊緣時(shí)間的損失,最好采取積累時(shí)間,然后分固定片更新動(dòng)畫的方式。

作者:戴嘉華

轉(zhuǎn)載請(qǐng)注明出處,保留原文鏈接和作者信息

目錄

前言

基于幀的動(dòng)畫算法(Frame-based)

基于時(shí)間的動(dòng)畫算法(Time-based)

改良基于時(shí)間的動(dòng)畫算法

總結(jié)

前言

前段時(shí)間無聊或有聊地做了幾個(gè)移動(dòng)端的HTML5游戲。放在不同的移動(dòng)端平臺(tái)上進(jìn)行測(cè)試后有了詭異的發(fā)現(xiàn),有些手機(jī)的動(dòng)畫會(huì)“快”一點(diǎn),有些手機(jī)的動(dòng)畫會(huì)“慢”一點(diǎn),有些慢得還不是一兩點(diǎn)。

通過查找資料發(fā)現(xiàn),基于幀的算法(Frame-based)來實(shí)現(xiàn)動(dòng)畫會(huì)導(dǎo)致不同幀率的平臺(tái)體驗(yàn)不一致,而基于時(shí)間(Time-based)的動(dòng)畫算法可以很好地改良這種情況,讓不同幀率的情況下都能達(dá)到較為統(tǒng)一的速度上的體驗(yàn)。

本文介紹的就是基于幀動(dòng)畫算法和基于時(shí)間動(dòng)畫算法的差異,以及對(duì)基于時(shí)間算法的改良。

基于幀的動(dòng)畫算法(Frame-based)

相信做過前端的人對(duì)使用JavaScript實(shí)現(xiàn)動(dòng)畫的原理都很熟悉。現(xiàn)在讓你實(shí)現(xiàn)一個(gè)讓一個(gè)div從左到右來回移動(dòng)的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/

看看代碼,我們讓一個(gè)div在0 ~ 300px區(qū)間內(nèi)左右來回移動(dòng)。update計(jì)算更新描繪div的位置,draw重新描繪頁(yè)面上的div。為了方便起見,這里直接使用setInterval作為定時(shí)器,實(shí)際情況下可以采用你喜歡的setTimeout或者requestAnimationFrame。這里設(shè)置每秒鐘到更新60次,60fps是人盡皆知的比較適合做動(dòng)畫的幀率。

地球人都知道,JavaScript中的定時(shí)器是不準(zhǔn)確的。由于JavaScript運(yùn)行時(shí)需要耗費(fèi)時(shí)間,而JavaScript又是單線程的,所以如果一個(gè)定時(shí)器如果比較耗時(shí)的話,是會(huì)阻塞下一個(gè)定時(shí)器的執(zhí)行。所以即使你這里設(shè)置了1000 / 60每秒60幀的幀率,在不同的瀏覽器平臺(tái)的差異也會(huì)導(dǎo)致實(shí)際上你的沒有60fps的幀率。

所以上面代碼在一個(gè)手機(jī)上執(zhí)行的時(shí)候可能有60fps的幀率,在另外一個(gè)手機(jī)上可能就只有30fps,更甚可能只有10fps。

我們模擬一下這種情況會(huì)有什么效果發(fā)生:

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

這完全不對(duì)大頭!

可以看到三個(gè)方塊移動(dòng)速度根本不在同一個(gè)channel上。想象一下一個(gè)超級(jí)馬里奧游戲在10fps的情況會(huì)怎么樣?按跳躍一下,你會(huì)看到馬里奧以一種太空漫游的姿態(tài)在空中拋弧線。

導(dǎo)致這種情況的原因很簡(jiǎn)單,因?yàn)槲覀冇?jì)算和繪制每個(gè)div位置的時(shí)候是在每幀更新,每幀移動(dòng)2px。在60fps的情況下,我們1秒鐘會(huì)執(zhí)行60幀,所以小塊每秒鐘會(huì)移動(dòng)60 * 2 = 120px;如果是30fps,小塊每秒就移動(dòng)30 * 2 = 60px,以此類推10fps就是每秒移動(dòng)20px。

三個(gè)小塊在單位時(shí)間內(nèi)移動(dòng)的距離不一樣!

假如你現(xiàn)在要做一個(gè)超級(jí)馬里奧的游戲,怎么做到可以在不同幀率的情況下讓馬里奧看起來還是那么迅速且?guī)洑猓?/p>

解決方案很明顯。雖然不同的瀏覽器平臺(tái)上的運(yùn)行差異可能會(huì)導(dǎo)致幀率的不一致,但是有一樣?xùn)|西是在任何平臺(tái)上都一致的,那就是時(shí)間。所以我們可以改良我們的算法,不是以幀為基準(zhǔn)來更新方塊的位置,而是以時(shí)間為單位更新。也就是說,我們之前是px/frame,現(xiàn)在換成px/ms

這就是接下來要說的基于時(shí)間(Time-based)的動(dòng)畫算法。

基于時(shí)間的動(dòng)畫算法(Time-based)

其實(shí)思路和實(shí)現(xiàn)都很簡(jiǎn)單。我們計(jì)算每一幀離上一幀過去了多少時(shí)間,然后根據(jù)過去的時(shí)間來更新方塊的位置。

例如,上面的方塊應(yīng)該每秒鐘移動(dòng)120px,每毫秒移動(dòng)120 / 1000 = 0.12像素(12px/ms)。如果上一幀方塊的位置在left為10px的位置,到了這一幀的時(shí)候,假設(shè)相對(duì)于上一幀來說時(shí)間過去了200ms,那在時(shí)間上來說在這一幀方塊應(yīng)該移動(dòng)200ms * 0.12px/ms = 240px。最終位置應(yīng)該為10 + 240 = 250px。其實(shí)就是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; // 計(jì)算時(shí)間差
            previous = current;
            update(dt);
            draw()
        }

        function update(dt) {
            left += param * (dt * 0.12); // 根據(jù)時(shí)間差更新位置
            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好像能勉強(qiáng)趕上60fps的步伐。但是時(shí)間久了會(huì)發(fā)現(xiàn)30fps和10fps越來越落后于60fps。(建議先刷新再看看效果會(huì)更加明顯)

這是因?yàn)?strong>每次小方塊碰到邊緣的時(shí)候,都會(huì)損失掉一部分時(shí)間,而且?guī)试降偷膿p失越大。看看我們上面的update函數(shù):

      function update(dt) {
          left += param * (dt * 0.12); // 根據(jù)時(shí)間差更新位置
          if (left > 300) {
              left = 300;
              param = -1;
          } else if (left < 0) {
              left = 0;
              param = 1;
          }
      }

假如我們現(xiàn)在方塊的位置在left為290px的位置,這一幀傳入的dt為100ms,那么我們left為290 + 100 * 0.12 = 302,但是302大于300,所以left會(huì)被設(shè)置為300。那么本來用來移動(dòng)2px的時(shí)間就會(huì)白白被“拋棄”掉。dt越大,浪費(fèi)得越多,所以30fps和10fps會(huì)比60fps越來越慢。

為了解決這個(gè)問題,我們對(duì)已有的算法進(jìn)行改良。

改良基于時(shí)間的動(dòng)畫算法

解決思路如下:不一次算整塊的時(shí)間(dt)移動(dòng)的距離,而是把dt分成固定的時(shí)間片,通過多次update固定的時(shí)間片來計(jì)算dt時(shí)間后應(yīng)該到什么位置。

比較抽象,我們直接看代碼:

    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; // 累積過去的時(shí)間
            while(acc >= dt) { // 當(dāng)時(shí)間大于我們的固定的時(shí)間片的時(shí)候可以進(jìn)行更新
                update(dt); // 分片更新時(shí)間
                acc -= dt;
            }
            draw();
        }

        // update 和 draw 函數(shù)不變
        setInterval(loop, 1000 / fps);
    }

我們先確定一個(gè)固定更新的時(shí)間片,如固定為60fps時(shí)一幀的時(shí)間:1000 / 60 = 0.167ms。然后積累過去的時(shí)間,然后根據(jù)固定時(shí)間片分片進(jìn)行更新。也就說,即使這一幀和上一幀相差過去了100ms,我也會(huì)把這100ms分成很多個(gè)0.167ms來執(zhí)行update函數(shù)。這樣做有兩個(gè)好處:

固定的時(shí)間片足夠小,更新的時(shí)候可以減少邊緣損失的時(shí)間。

不同幀率,不管你是60,30,還是10fps,也是根據(jù)固定時(shí)間片來執(zhí)行update函數(shù),所以即使有損失,不同幀率之間的損失是一樣的。那么我們?nèi)齻€(gè)方塊就可以達(dá)到同步移動(dòng)的效果的了!

看上面的代碼,update和draw函數(shù)保持不變,而loop函數(shù)中,對(duì)過去的時(shí)間進(jìn)行了累加,當(dāng)時(shí)間超過固定的片就可以執(zhí)行update。while循環(huán)可以保證更新直到把積累的時(shí)間都更新完。

對(duì)時(shí)間進(jìn)行積累,然后分固定片更新。這種方式還有一個(gè)非常大的好處,如果你的幀率超過了60fps,如達(dá)到100fps或者200fps,這時(shí)候passed會(huì)小于0.167ms,時(shí)間就會(huì)被積累,積累大于0.167才會(huì)執(zhí)行更新。碉堡的效果就是:不管你的幀率是高還是低,移動(dòng)速度都可以和60fps情況下的速度同步。

看看最后的效果:

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

還是蠻不錯(cuò)的。

總結(jié)

基于幀的動(dòng)畫算法會(huì)在幀率不同的情況下導(dǎo)致動(dòng)畫體驗(yàn)有較大的差異,所有動(dòng)畫都應(yīng)該基于時(shí)間進(jìn)行執(zhí)行。而基于時(shí)間的動(dòng)畫算法要注意邊緣時(shí)間的損失,最好采取積累時(shí)間,然后分固定片更新動(dòng)畫的方式。

References

http://gafferongames.com/game-physics/fix-your-timestep/

http://blog.sklambert.com/using-time-based-animation-implement/

http://viget.com/extend/time-based-animation

http://codetheory.in/time-based-animations-in-html5-games-why-and-how-to-implement-them/

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/85421.html

相關(guān)文章

  • js設(shè)計(jì)模式--策略模式

    摘要:將不變的部分和變化的部分隔開是每個(gè)設(shè)計(jì)模式的主題,策略模式也不例外,策略模式的目的就是將算法的使用與算法的實(shí)現(xiàn)分離開來。 前言 本系列文章主要根據(jù)《JavaScript設(shè)計(jì)模式與開發(fā)實(shí)踐》整理而來,其中會(huì)加入了一些自己的思考。希望對(duì)大家有所幫助。 文章系列 js設(shè)計(jì)模式--單例模式 js設(shè)計(jì)模式--策略模式 js設(shè)計(jì)模式--代理模式 概念 策略模式的定義是:定義一系列的算法,把它們一個(gè)...

    bigdevil_s 評(píng)論0 收藏0
  • 校招社招必備核心前端面試問題與詳細(xì)解答

    摘要:本文總結(jié)了前端老司機(jī)經(jīng)常問題的一些問題并結(jié)合個(gè)人總結(jié)給出了比較詳盡的答案。網(wǎng)易阿里騰訊校招社招必備知識(shí)點(diǎn)。此外還有網(wǎng)絡(luò)線程,定時(shí)器任務(wù)線程,文件系統(tǒng)處理線程等等。線程核心是引擎。主線程和工作線程之間的通知機(jī)制叫做事件循環(huán)。 showImg(https://segmentfault.com/img/bVbu4aB?w=300&h=208); 本文總結(jié)了前端老司機(jī)經(jīng)常問題的一些問題并結(jié)合個(gè)...

    jonh_felix 評(píng)論0 收藏0
  • 校招社招必備核心前端面試問題與詳細(xì)解答

    摘要:本文總結(jié)了前端老司機(jī)經(jīng)常問題的一些問題并結(jié)合個(gè)人總結(jié)給出了比較詳盡的答案。網(wǎng)易阿里騰訊校招社招必備知識(shí)點(diǎn)。此外還有網(wǎng)絡(luò)線程,定時(shí)器任務(wù)線程,文件系統(tǒng)處理線程等等。線程核心是引擎。主線程和工作線程之間的通知機(jī)制叫做事件循環(huán)。 showImg(https://segmentfault.com/img/bVbu4aB?w=300&h=208); 本文總結(jié)了前端老司機(jī)經(jīng)常問題的一些問題并結(jié)合個(gè)...

    Rango 評(píng)論0 收藏0
  • 【翻譯】javascript 動(dòng)畫原理淺析

    摘要:動(dòng)畫原文通常,框架會(huì)為你處理動(dòng)畫。整個(gè)的動(dòng)畫過程被分成了很小的步驟,每一個(gè)步驟被定時(shí)器調(diào)用。因?yàn)槎〞r(shí)器的周期非常短,所以動(dòng)畫看起來是連續(xù)的。已經(jīng)過去的動(dòng)畫時(shí)間作為分子,計(jì)算每一幀通過公式。線性的使動(dòng)畫以固定的速度進(jìn)行。 動(dòng)畫 原文:http://javascript.info/tutori... 通常,框架會(huì)為你處理動(dòng)畫。但是,你可能想知道僅僅用javascript怎么來實(shí)現(xiàn)動(dòng)畫,和可...

    Worktile 評(píng)論0 收藏0
  • 校招社招必備核心前端面試問題與詳細(xì)解答

    摘要:本文總結(jié)了前端老司機(jī)經(jīng)常問題的一些問題并結(jié)合個(gè)人總結(jié)給出了比較詳盡的答案。網(wǎng)易阿里騰訊校招社招必備知識(shí)點(diǎn)。此外還有網(wǎng)絡(luò)線程,定時(shí)器任務(wù)線程,文件系統(tǒng)處理線程等等。線程核心是引擎。主線程和工作線程之間的通知機(jī)制叫做事件循環(huán)。 showImg(https://segmentfault.com/img/bVbu4aB?w=300&h=208); 本文總結(jié)了前端老司機(jī)經(jīng)常問題的一些問題并結(jié)合個(gè)...

    DevTalking 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<