利用setTimeout实现的倒计时功能出现跳一秒问题

var period = 60 * 1000 * 60 * 2
var end
var date = new Date(end)
var interval = 1000
var loop = function () {
  if (!end) { end = new Date().getTime() + period }
  var diff = end - new Date().getTime()
  var h = Math.floor(diff / (60 * 1000 * 60))
  var hdiff = diff % (60 * 1000 * 60)
  var m = Math.floor(hdiff / (60 * 1000))
  var mdiff = hdiff % (60 * 1000)
  var s = Math.floor(mdiff / (1000))
  console.log(h, m, s)
  setTimeout(loop, interval)
}
setTimeout(loop, interval)

上面是我写的一个倒计时方法,在控制台打印出的结果却总是跳了一秒,如下所示:

clipboard.png

不知这是为何?

阅读 5.9k
6 个回答

解决了,第一次执行方法的时候就已经过了一秒了,所以在初始化end变量的时候就应该减去一秒!

var period = 60 * 1000 * 60 * 2
var end
var date = new Date(end)
var interval = 1000
function loop() {
  if (!end) { end = new Date().getTime() + period - interval / 1000 }
  var diff = end - new Date().getTime()
  var h = Math.floor(diff / (60 * 1000 * 60))
  var hdiff = diff % (60 * 1000 * 60)
  var m = Math.floor(hdiff / (60 * 1000))
  var mdiff = hdiff % (60 * 1000)
  var s = Math.floor(mdiff / (1000))
  console.log(h, m, s)
  setTimeout(loop, interval)
}
setTimeout(loop, interval)

上述代码会造成执行代码的时间累加问题,为了解决这个问题,参考这篇文章,更精确的倒计时应该是这样的:

var period = 60 * 1000 * 60 * 2
var startTime = new Date().getTime();
var count = 0
var end = new Date().getTime() + period
var interval = 1000
var currentInterval = interval

function loop() {
  count++
  var offset = new Date().getTime() - (startTime + count * interval); // 代码执行所消耗的时间
  var diff = end - new Date().getTime()
  var h = Math.floor(diff / (60 * 1000 * 60))
  var hdiff = diff % (60 * 1000 * 60)
  var m = Math.floor(hdiff / (60 * 1000))
  var mdiff = hdiff % (60 * 1000)
  var s = mdiff / (1000)
  var sCeil = Math.ceil(s)
  var sFloor = Math.floor(s)
  currentInterval = interval - offset // 得到下一次循环所消耗的时间
  console.log('时:'+h, '分:'+m, '毫秒:'+s, '秒向上取整:'+sCeil, '代码执行时间:'+offset, '下次循环间隔'+currentInterval) // 打印 时 分 秒 代码执行时间 下次循环间隔

  setTimeout(loop, currentInterval)
}

setTimeout(loop, currentInterval)

感谢各位不吝赐教。

这个需求其实用setInterval更好,还不会出现你上述的问题:

var period = 60 * 1000 * 60 * 2
var end
var date = new Date(end)
var interval = 1000
var loop = function () {
  if (!end) { end = new Date().getTime() + period }
  var diff = end - new Date().getTime()
  var h = Math.floor(diff / (60 * 1000 * 60))
  var hdiff = diff % (60 * 1000 * 60)
  var m = Math.floor(hdiff / (60 * 1000))
  var mdiff = hdiff % (60 * 1000)
  var s = Math.floor(mdiff / (1000))
  console.log(h, m, s)
}
setInterval(loop, interval)

这是个膜法问题啊...

js的所有异步时间都不是精确的。
观察一下可以看到每次都不是间隔1000毫秒,
一般的做法是储存下来diff数值,然后每次循环 从diff中减去period

可以参考我下面的代码,其中countDown函数接收倒计时的起始时间和结束时间(ms)。

其基本思路时倒推结束时间之前的所有计时时刻,setTimeout调用时,其时间延迟根据当前时刻与下一个计时时刻的时间差来设置,而不是一个固定值,这样就能避免累计误差。

参数可以比较灵活, 比如起始时间可以是函数调用的当前时间之前,如countDown(Date.now()-5000, Date.now() + 2 * 3600 * 1000),这样的话程序一开始会连续输出已经过去的所有计时时刻;如果起始时间在调用时间之后,那么会等到那个时间之后才开始倒计时,比如countDown(Date.now()+5000, Date.now() + 2 * 3600 * 1000)


(function(){
    var now = Date.now();
    countDown(now, now + 2 * 3600 * 1000);

    function countDown(start, end){
        var interval = 1000,
            nextTick = start + ((end - start) % interval);
        var loopTime = function(){
            var now = Date.now();
            while(now >= nextTick){
                showTime(end - nextTick);
                nextTick += interval;
            }
            if(nextTick <= end){
                setTimeout(arguments.callee, nextTick - now);
            }
        };
        loopTime();
    }
    function showTime(ms){
        var sTotal = Math.floor(ms/1000),
            h = Math.floor(sTotal/3600),
            m = Math.floor(sTotal%3600/60),
            s = Math.floor(sTotal%60),
            timeStr = ('0' + h).slice(-2) + ':' +
                    ('0' + m).slice(-2) + ':' +
                    ('0' + s).slice(-2);
        console.log(timeStr);
    }
})();
var period = 60 * 1000 * 60 * 2
var end
var date = new Date(end)
var interval = 100
var diff,h,hdiff,m,mdiff,s, res, res_tmp;
var loop = function () {
  if (!end) { end = new Date().getTime() + period }
  diff = end - new Date().getTime()
  h = Math.floor(diff / (60 * 1000 * 60))
  hdiff = diff % (60 * 1000 * 60)
  m = Math.floor(hdiff / (60 * 1000))
  mdiff = hdiff % (60 * 1000)
  s = Math.floor(mdiff / (1000))
  res_tmp = [h, m, s].join('-');
  if (!res || res != res_tmp) {
    res = res_tmp;
    console.log(res)
  }
  setTimeout(loop, interval)
}
setTimeout(loop, interval)

降低时间间隔。来排除setTimeout带来的偏差累计

推荐问题