關於做常規的數字定時滾動效果

陳雅遜發表於2018-11-06

目前專案團隊在做一個接受到的數字要做成實現數字滾動效果 在輪詢的基礎上做的 就是上一個數字到目前最新的數字的變化 我想到的就是用使用setTimeout定時器,敬請讀到文章結尾再提出自己的意見,前面都是不完善的思路,最後尚可用

/**
* 定時器方法
* @param num {number} 當前值
* @param digitalBeating {number} 上一個值
* @param diff {number} num - digitalBeating的差值
*/
transNum(num,digitalBeating,diff) {
    let i = digitalBeating,timeout = null;
    const animate = () => {
       if (i<num) {
        i++
        timeout = setTimeout(animate,1000/diff)
       } else {
           digitalBeating = num
       }
       let numList = i.toString().split('')   // 這裡是為了以陣列的形式單獨展示每一個數字
    }
    animate()
}
複製程式碼

那麼寫到這裡是不是就可以簡單實現了呢 其實還有一段路要走,比如你考慮了效能問題了嗎

transNum(num,digitalBeating,diff) {
    let i = digitalBeating,timeout = null;
    const animate = () => {
       if (timeout) {
           clearTimeout(timeout)
           timeout = null
       }
       if (i<num) {
        i++
        timeout = setTimeout(animate,1000/diff)
       } else {
           digitalBeating = num
       }
       let numList = i.toString().split('')   // 這裡是為了以陣列的形式單獨展示每一個數字
    }
    animate()
}
複製程式碼

上一步我們增加了防抖就是在遞迴呼叫當上一個setTimeout還存在的時候 我們要清除掉setTimeout 以免造成干擾和卡頓現象 那麼是不是就可以了呢 你考慮過1秒之內setTimeout也有極限的嗎 根據各個瀏覽器的效能差異,應該控制在20ms~30ms之間,那麼我們進行下一步的改造

transNum(num,digitalBeating,diff) {
    let i = digitalBeating,timeout = null,steps = 1;
    const animate = () => {
       if (timeout) {
           clearTimeout(timeout)
           timeout = null
       }
       if (i<num) {
        if(diff<=50) {
            steps = 1
        } else if (diff=<100&&diff>50) {
            steps = 2
        } else if (diff>100&&diff<150) {
            steps = 3
        } else if ...
        
        i += steps
        timeout = setTimeout(animate,1000*steps/diff)
       } else {
           digitalBeating = num
       }
       let numList = i.toString().split('')   // 這裡是為了以陣列的形式單獨展示每一個數字
    }
    animate()
}
複製程式碼

我們定義了一個變數steps作為步長來保證1秒之內呼叫的次數,可是以上的行為會不會很蠢 因為你沒辦法知道這個diff差值到底是多少 沒有封頂的 所以我們繼續改造

transNum(num,digitalBeating,diff) {
    let i = digitalBeating,
    timeout = null,
    len = diff.toString().length,
    steps = Math.pow(10,len-2); // 這裡步長設定最為關鍵,這樣取值可保證頻率(<100)
    const animate = () => {
       if (timeout) {
           clearTimeout(timeout)
           timeout = null
       }
       if (i<num) {
        if(diff<10) {
            steps = 1
        }
        if (diff/steps>50) {
            steps *= 2
        }
        
        i += steps
        timeout = setTimeout(animate,1000*steps/diff)
       } else {
           digitalBeating = num
       }
       let numList = i.toString().split('')   // 這裡是為了以陣列的形式單獨展示每一個數字
    }
    animate()
}
複製程式碼

目前來看大概就可以保證在1秒之內呼叫次數50次了,不過呢,你可能會發現最後一次的數字變動可能是i>num了 那麼肯定就不是我們想要的結果了,就在最後一次再加個判斷吧

transNum(num,digitalBeating,diff) {
    let i = digitalBeating,
    timeout = null,
    len = diff.toString().length,
    steps = Math.pow(10,len-2);  // 這裡步長設定最為關鍵,這樣取值可保證頻率(<100)
    const animate = () => {
       if (timeout) {
           clearTimeout(timeout)
           timeout = null
       }
       if (i<num) {
        if(diff<10) {
            steps = 1
        }
        if (diff/steps>50) {
            steps *= 2
        }
        
        i += steps
        timeout = setTimeout(animate,1000*steps/diff)
        // 這裡作為最後一次判斷 如果最後一次的i和num的差距小於steps時 肯定會跳過了
        if (num-i<steps) {
            i = num
        }
       } else {
           digitalBeating = num
       }
       let numList = i.toString().split('')   // 這裡是為了以陣列的形式單獨展示每一個數字
    }
    animate()
}
複製程式碼

再來看看 是不是基本上能滿足需求了呢 那麼你能保證不會延遲嗎 所以應該規定在1秒之內必須跑完了

transNum(num,digitalBeating,diff) {
    let i = digitalBeating,
    timeout = null,
    len = diff.toString().length,
    steps = Math.pow(10,len-2),      // 這裡步長設定最為關鍵,這樣取值可保證頻率(<100)
    n1 = 10,       // 設定兩個限制條件n1,n2 可以進一步規範步長(>=1)和頻率(<50次)
    n2 = 50,
    _lastTime = new Date();   // 設定初始時間
    const animate = () => {
       if (timeout) {
           clearTimeout(timeout)
           timeout = null
       }
       let _nowTime = new Date()  // 這一輪呼叫時的時間
       // 在這裡統一判斷 
       if (i<num && num-i>=steps && _nowTime - _lastTime<1000) {
        if(diff<n1) {
            steps = 1
        }
        if (diff/steps>n2) {
            steps *= 2
        }
        
        i += steps
        timeout = setTimeout(animate,1000*steps/diff)
       } else {
           i = num
           clearTimeout(timeout)
           timeout = null
           digitalBeating = num
       }
       let numList = i.toString().split('')   // 這裡是為了以陣列的形式單獨展示每一個數字
    }
    animate()
}
複製程式碼

唔,現在也許可行了吧 然而做了這麼多 其實還是不夠完美的 一個是數字滾動就不是一個數字一個數字的滾了 而且用了定時器了 這個本身就是耗費效能的事 有沒有更優的辦法呢 當然有 請期待用css transition 3d來實現

相關文章