欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

js倒计时的三种写法

最编程 2024-02-23 15:39:22
...

1、requestAnimationFrame

通常是每秒 60 次,在大多数遵循 W3C 建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配,时间间隔小于16ms不适用
chrome和手机上实验,页面即使在后台,requestAnimationFrame也在继续执行

(function() {
    // requestAnimationFrame
    let requestAnimationFrameTimer = 0
    let start = new Date().getTime(); //获取开始时间
    let timeout = 1000
    let agoTime = 0
    let countIndex = 0
    function showTime(timeDiff) { // 剩余时间
            let end = new Date().getTime(); //获取当前时间
            agoTime = end - start
            if (agoTime >= countIndex * timeout) { // 大于1s
                    if(countIndex > 0) {
                            timeDiff -= timeout
                    }
                    countIndex++
                    if (timeDiff <= 0) {
                            window.cancelAnimationFrame(requestAnimationFrameTimer)
                            return
                    }
                    let temp = Math.round(timeDiff / 1000)
                    //获取还剩多少天
                    let day = Math.floor(temp / 60 / 60 / 24);
                    //获取还剩多少小时
                    let hour = Math.floor((temp / 60 / 60) % 24);
                    //获取还剩多少分钟
                    let minute = Math.floor((temp / 60) % 60);
                    //获取还剩多少秒
                    let second = Math.floor(temp % 60);
                    //给小于10的数值前面加 0
                    function timerFilter(params) {
                            if (params - 0 < 10) {
                                    return "0" + params;
                            } else {
                                    return params;
                            }
                    }
                    //输出还剩多少时间
                    let str = day + "天" + timerFilter(hour) + ":" + timerFilter(minute) + ":" + timerFilter(second)
                    console.log(str);
                    // document.getElementById('time1').innerHTML = str
            }
            requestAnimationFrameTimer = window.requestAnimationFrame(() => {
                    showTime(timeDiff);
            });
    }
    showTime(10000);
}());

image.png

2、setTimeout

  • setTimeout/setInterval回调触发超时的原因
  • 使用setInterval要确保执行持续时间短于间隔频率,计时列队机制是:假如我设置时间间隔是10ms,setInterval是上一个任务开始后10ms 开始执行任务列队的下一个任务。如果上一个任务用了15ms 才执行完,下一个任务不会在第25ms 处执行,而是会在15ms 处立刻执行,为了确保两次执行之间有固定的间隔,建议用setTimeout而不是setInterval
// 使用setTimeout
;(function(){
    let timer = ''
    let timeDiff = 0
    let startTime = 0
    let countIndex = 1
    let timeout = 1000
    function showTime(timeDiff, interval) {
            timer && clearTimeout(timer);
            if (timeDiff <= 0) {
                    timer && clearTimeout(timer)
                    timeDiff = 0
                    startTime = 0
                    countIndex = 1
                    return
            }
            //获取还剩多少天
            let day = Math.floor(timeDiff / 1000 / 60 / 60 / 24);
            //获取还剩多少小时
            let hour = Math.floor((timeDiff / 1000 / 60 / 60) % 24);
            //获取还剩多少分钟
            let minute = Math.floor((timeDiff / 1000 / 60) % 60);
            //获取还剩多少秒
            let second = Math.floor((timeDiff / 1000) % 60);
            //输出还剩多少时间
            let str = day + "天" + timerFilter(hour) + ":" + timerFilter(minute) + ":" + timerFilter(second)
            console.log(str);
            // document.getElementById('time2').innerHTML = str
            //给小于10的数值前面加 0
            function timerFilter(params) {
              if (params - 0 < 10) {
                    return "0" + params;
              } else {
                    return params;
              }
            }
            timer = setTimeout(() => {
                    const endTime = new Date().getTime();
                    const difference = endTime - (startTime + countIndex * timeout)
                    console.log(`${countIndex}: 偏差${difference}ms`);
                    countIndex++
                    showTime(timeDiff - 1000, timeout - difference)
            }, interval)
    }
    startTime = new Date() * 1
    showTime(10000, interval = timeout);
})()

image.png

3、web worker

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程

;(function() {
    // web worker
    function getWorker(worker, param) {
            const code = worker.toString()
            const blob = new Blob([`(${code})(${JSON.stringify(param)})`])

            return new Worker(URL.createObjectURL(blob))
    }

    function getCountDown(param) {
            let _timer = null
            let {
                    leftTime
            } = param

            this.onmessage = e => {
                    const {
                            data: {
                                    type,
                                    interval
                            }
                    } = e

                    const countDown = (milsecond) => {
                            const second = milsecond / 1000

                            if (milsecond <= 0) {
                                    clearInterval(_timer)
                                    return [0, 0, 0, 0]
                            }
                            // ~是js里的按位取反操作符 , ~~ 就是执行两次按位取反,其实就是保持原值,但是注意虽然是原值
                            // 但是对布尔型变量执行这个操作,会转化成相应的数值型变量,也就是 ~~true === 1
                            const leftSecond = ~~(second % 60)
                            const leftMinutes = ~~(second / 60 % 60)
                            const leftHours = ~~(second / 60 / 60 % 24)
                            const leftDay = ~~(second / 60 / 60 / 24)
                            return [leftDay, leftHours, leftMinutes, leftSecond]
                    }

                    nextTick = countDown(leftTime)

                    if (type === 'start') {
                            this.postMessage({
                                    nextTick
                            })
                            _timer = setInterval(() => {
                                    leftTime = leftTime - interval
                                    nextTick = countDown(leftTime)
                                    // 通过postMessage告诉父进程 我已经计时了1秒了 你可以操作了
                                    if (leftTime > 0) {
                                            this.postMessage({
                                                    nextTick
                                            })
                                    }
                            }, interval)
                    } else if (type === 'end') {
                            clearInterval(_timer)
                    }
            }
    }
    const worker = getWorker(getCountDown, {
            leftTime: 10000
    })

    worker.postMessage({
            type: "start",
            interval: 1000
    })
    worker.onmessage = e => render()(e.data.nextTick)

    // 渲染方法
    function render() {
            const elem = document.getElementById('time3')

            return function(params = [0, 0, 0, 0]) {

                    temp = params.slice(1).map(item => {
                            return item < 10 ? '0' + item : item
                    })
                    let str = params[0] + '天' + temp.join(':')
                    console.log('倒计时:', str);
                    // elem.innerHTML = str
            }
    }
})()

思考

可以采用后端返回剩余时间,前端定时请求接口获取剩余时间避免误差

参考

JavaScript 倒计时踩坑集锦
JS倒计时setTimeout为什么会出现误差
利用 requestAnimationFrame 实现 setInterval 计时器
日常开发: Web Worker实现秒杀倒计时
如何使倒计时计时器的性能达到最优?
vue实现倒计时的代码(纯代码)