如何暂停 setInterval() 函数?

92
我如何使用JavaScript暂停和恢复setInterval()函数?
例如,我有一个秒表来告诉你浏览网页的秒数。有一个“暂停”和“恢复”按钮。之所以在此处clearInterval()不起作用,是因为如果用户在第40秒和800毫秒点击“暂停”按钮,当他再次单击“恢复”按钮时,经过的秒数必须在200毫秒后增加1秒。如果我在定时器变量上使用clearInterval()函数(在点击暂停按钮时),然后再次在定时器变量上使用setInterval()函数(在单击恢复按钮时),所经过的秒数将仅在1000毫秒后增加1秒,这会破坏秒表的准确性。
那么我该怎么做呢?

1
可能是一个简单的JavaScript倒计时器的代码?的重复。 - HIRA THAKUR
2
可能是如何在JavaScript中暂停windows.setInterval?的重复问题。 - elclanrs
1
请看上面提到的非常相似的问题中的这个答案 - Tibos
10个回答

149
您可以使用一个标志来跟踪状态:
```

您可以使用一个标志来跟踪状态:

```

var output = $('h1');
var isPaused = false;
var time = 0;
var t = window.setInterval(function() {
  if(!isPaused) {
    time++;
    output.text("Seconds: " + time);
  }
}, 1000);

//with jquery
$('.pause').on('click', function(e) {
  e.preventDefault();
  isPaused = true;
});

$('.play').on('click', function(e) {
  e.preventDefault();
  isPaused = false;
});
h1 {
    font-family: Helvetica, Verdana, sans-serif;
    font-size: 12px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>Seconds: 0</h1>
<button class="play">Play</button>
<button class="pause">Pause</button>

这只是我会做的事情,我不确定你是否可以暂停setInterval。
注意:对于不需要高精度的应用程序,此系统简单易用且效果良好,但它不考虑滴答之间经过的时间:如果您在半秒钟后单击暂停,稍后再单击播放,您的时间将不准确。

1
如果您使用.pause.play进行多次click,则最终会被时间所超越。因此,无法预测在哪个n/1000部分将发生click。这样会导致损失n/1000秒或获得(1000-n)/1000秒的时间。但在某些情况下,这并没有意义,但在一些惊险场景中,它们会造成很大的伤害。 - vinrav
1
无论如何,我喜欢这个想法(简单而干净),我正在将其与我的代码一起使用,其中没有杀死任何人。这条评论只是为了通知那些打算用他们的脚本杀死某个人的人。 - vinrav
1
不错的技巧。感谢您的回答,但您没有暂停 setInterval 函数。 - Ilyas karim
4
setInterval函数无法暂停,只能通过停止它(clearInterval)或让它继续运行来实现。我提供的解决方案是为了解决OP的问题,我并没有声称它适用于所有可能的情况。 - Jonas Grumann
那并没有解决他的问题。结果仍然是概率性的。更好的选择是清除间隔,并增加自上次触发以来经过的时间。间隔事件可能会将其增加一,而暂停事件可能会将其增加0.42。但您还需要存储每个间隔触发的时间,以便进行比较。Date.now()是您想要的函数。 - Jason Mitchell

18

在间隔函数中不应该测量时间。相反,只需保存计时器启动时的时间,并在计时器停止/暂停时测量差异。仅使用setInterval来更新显示的值。因此,不需要暂停计时器,这样可以获得最佳精度。


1
为什么不应该用时间间隔来测量时间?如果他们想要显示实时计时器/倒计时,则每秒都需要重新计算经过的时间,为什么不在那里跟踪它呢? - Hartley Brody

12

尽管 @Jonas Giuro 的说法是正确的:

您无法暂停 setInterval 函数,您只能停止它(clearInterval)或让它运行

另一方面,可以使用VitaliyG 建议的方法模拟此行为:

不应在 interval 函数中测量时间。相反,只需保存计时器启动时的时间并在计时器停止/暂停时测量差异。仅使用 setInterval 更新显示的值。

var output = $('h1');
var isPaused = false;
var time = new Date();
var offset = 0;
var t = window.setInterval(function() {
  if(!isPaused) {
    var milisec = offset + (new Date()).getTime() - time.getTime();
    output.text(parseInt(milisec / 1000) + "s " + (milisec % 1000));
  }
}, 10);

//with jquery
$('.toggle').on('click', function(e) {
  e.preventDefault();
  isPaused = !isPaused;
  if (isPaused) {
    offset += (new Date()).getTime() - time.getTime();
  } else {
    time = new Date();
  }

});
h1 {
    font-family: Helvetica, Verdana, sans-serif;
    font-size: 12px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h1>Seconds: 0</h1>
<button class="toggle">Toggle</button>


9

我写了一个简单的 ES6 类,可能会很实用。
灵感来自于https://dev59.com/uGEi5IYBdhLWcg3wb76O#58580918的回答。

export class IntervalTimer {
    callbackStartTime;
    remaining = 0;
    paused = false;
    timerId = null;
    _callback;
    _delay;

    constructor(callback, delay) {
        this._callback = callback;
        this._delay = delay;
    }

    pause() {
        if (!this.paused) {
            this.clear();
            this.remaining = new Date().getTime() - this.callbackStartTime;
            this.paused = true;
        }
    }

    resume() {
        if (this.paused) {
            if (this.remaining) {
                setTimeout(() => {
                    this.run();
                    this.paused = false;
                    this.start();
                }, this.remaining);
            } else {
                this.paused = false;
                this.start();
            }
        }
    }

    clear() {
        clearInterval(this.timerId);
    }

    start() {
        this.clear();
        this.timerId = setInterval(() => {


            this.run();
        }, this._delay);
    }

    run() {
        this.callbackStartTime = new Date().getTime();
        this._callback();
    }
}

使用起来非常简单明了,

const interval = new IntervalTimer(console.log('aaa'), 3000);
interval.start();
interval.pause();
interval.resume();
interval.clear();

1
谢谢您提供这个信息,但是需要注意的是,您代码中的私有/公共修饰符来自TypeScript而不是ES6的一部分。 - eballeste

8

为什么不使用更简单的方法?添加一个类!

只需添加一个指示间隔不执行任何操作的类。例如:在悬停时。

var i = 0;
this.setInterval(function() {
  if(!$('#counter').hasClass('pauseInterval')) { //only run if it hasn't got this class 'pauseInterval'
    console.log('Counting...');
    $('#counter').html(i++); //just for explaining and showing
  } else {
    console.log('Stopped counting');
  }
}, 500);

/* In this example, I'm adding a class on mouseover and remove it again on mouseleave. You can of course do pretty much whatever you like */
$('#counter').hover(function() { //mouse enter
    $(this).addClass('pauseInterval');
  },function() { //mouse leave
    $(this).removeClass('pauseInterval');
  }
);

/* Other example */
$('#pauseInterval').click(function() {
  $('#counter').toggleClass('pauseInterval');
});
body {
  background-color: #eee;
  font-family: Calibri, Arial, sans-serif;
}
#counter {
  width: 50%;
  background: #ddd;
  border: 2px solid #009afd;
  border-radius: 5px;
  padding: 5px;
  text-align: center;
  transition: .3s;
  margin: 0 auto;
}
#counter.pauseInterval {
  border-color: red;  
}
<!-- you'll need jQuery for this. If you really want a vanilla version, ask -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


<p id="counter">&nbsp;</p>
<button id="pauseInterval">Pause</button></p>

我已经寻找这种快速简便的方法很久了,所以我会发布几个版本,以便尽可能多的人了解它。


5

我的简单方法:

function Timer (callback, delay) {
  let callbackStartTime
  let remaining = 0

  this.timerId = null
  this.paused = false

  this.pause = () => {
    this.clear()
    remaining -= Date.now() - callbackStartTime
    this.paused = true
  }
  this.resume = () => {
    window.setTimeout(this.setTimeout.bind(this), remaining)
    this.paused = false
  }
  this.setTimeout = () => {
    this.clear()
    this.timerId = window.setInterval(() => {
      callbackStartTime = Date.now()
      callback()
    }, delay)
  }
  this.clear = () => {
    window.clearInterval(this.timerId)
  }

  this.setTimeout()
}

使用方法:

let seconds = 0
const timer = new Timer(() => {
  seconds++
  
  console.log('seconds', seconds)

  if (seconds === 8) {
    timer.clear()

    alert('Game over!')
  }
}, 1000)

timer.pause()
console.log('isPaused: ', timer.paused)

setTimeout(() => {
  timer.resume()
  console.log('isPaused: ', timer.paused)
}, 2500)


function Timer (callback, delay) {
  let callbackStartTime
  let remaining = 0

  this.timerId = null
  this.paused = false

  this.pause = () => {
    this.clear()
    remaining -= Date.now() - callbackStartTime
    this.paused = true
  }
  this.resume = () => {
    window.setTimeout(this.setTimeout.bind(this), remaining)
    this.paused = false
  }
  this.setTimeout = () => {
    this.clear()
    this.timerId = window.setInterval(() => {
      callbackStartTime = Date.now()
      callback()
    }, delay)
  }
  this.clear = () => {
    window.clearInterval(this.timerId)
  }

  this.setTimeout()
}

代码编写很快,没有进行重构。如果您想让我改进代码并提供ES2015版本(类),请提高我的答案评分。

4

我知道这个帖子很老了,但这可能是另一种解决方案:

var do_this = null;

function y(){
   // what you wanna do
}

do_this = setInterval(y, 1000);

function y_start(){
    do_this = setInterval(y, 1000);
};
function y_stop(){
    do_this = clearInterval(do_this);
};

1
以下代码提供了一种精确的方法来暂停或恢复计时器。 工作原理: 当计时器在暂停后恢复时,它使用单个timeout生成一个correction cycle,该周期将考虑暂停偏移量(即计时器在周期之间被暂停的确切时间)。完成校正周期后,它使用常规的setInteval安排以下周期,并继续正常执行周期。
这样可以暂停/恢复计时器,而不会失去同步。 代码:

function Timer(_fn_callback_ , _timer_freq_){
    let RESUME_CORRECTION_RATE = 2;

    let _timer_statusCode_;
    let _timer_clockRef_;

    let _time_ellapsed_;        // will store the total time ellapsed
    let _time_pause_;           // stores the time when timer is paused
    let _time_lastCycle_;       // stores the time of the last cycle

    let _isCorrectionCycle_;
 
    /**
     * execute in each clock cycle
     */
    const nextCycle = function(){
        // calculate deltaTime
        let _time_delta_        = new Date() - _time_lastCycle_;
        _time_lastCycle_    = new Date();
        _time_ellapsed_   += _time_delta_;

        // if its a correction cicle (caused by a pause,
        // destroy the temporary timeout and generate a definitive interval
        if( _isCorrectionCycle_ ){
            clearTimeout( _timer_clockRef_ );
            clearInterval( _timer_clockRef_ );
            _timer_clockRef_    = setInterval(  nextCycle , _timer_freq_  );
            _isCorrectionCycle_ = false;
        }
        // execute callback
        _fn_callback_.apply( timer, [ timer ] );
    };

    // initialize timer
    _time_ellapsed_     = 0;
    _time_lastCycle_     = new Date();
    _timer_statusCode_   = 1;
    _timer_clockRef_     = setInterval(  nextCycle , _timer_freq_  );


    // timer public API
    const timer = {
        get statusCode(){ return _timer_statusCode_ },
        get timestamp(){
            let abstime;
            if( _timer_statusCode_=== 1 ) abstime = _time_ellapsed_ + ( new Date() - _time_lastCycle_ );
            else if( _timer_statusCode_=== 2 ) abstime = _time_ellapsed_ + ( _time_pause_ - _time_lastCycle_ );
            return abstime || 0;
        },

        pause : function(){
            if( _timer_statusCode_ !== 1 ) return this;
            // stop timers
            clearTimeout( _timer_clockRef_ );
            clearInterval( _timer_clockRef_ );
            // set new status and store current time, it will be used on
            // resume to calculate how much time is left for next cycle
            // to be triggered
            _timer_statusCode_ = 2;
            _time_pause_       = new Date();
            return this;
        },

        resume: function(){
            if( _timer_statusCode_ !== 2 ) return this;
            _timer_statusCode_  = 1;
            _isCorrectionCycle_ = true;
            const delayEllapsedTime = _time_pause_ - _time_lastCycle_;
            _time_lastCycle_    = new Date( new Date() - (_time_pause_ - _time_lastCycle_) );

            _timer_clockRef_ = setTimeout(  nextCycle , _timer_freq_ - delayEllapsedTime - RESUME_CORRECTION_RATE);

            return this;
        } 
    };
    return timer;
};


let myTimer = Timer( x=> console.log(x.timestamp), 1000);
<input type="button" onclick="myTimer.pause()" value="pause">
<input type="button" onclick="myTimer.resume()" value="resume">

代码源:

这个计时器是我创建的一个js库advanced-timer的修改和简化版本,拥有更多的功能。

完整的库和文档可以在NPMGITHUB中找到。


0

let time = document.getElementById("time");
let stopButton = document.getElementById("stop");

let timeCount = 0,
  currentTimeout;

function play() {
  stopButton.hidden = false;
  clearInterval(currentTimeout);
  currentTimeout = setInterval(() => {
    timeCount++;
    const min = String(Math.trunc(timeCount / 60)).padStart(2, 0);
    const sec = String(Math.trunc(timeCount % 60)).padStart(2, 0);
    time.innerHTML = `${min} : ${sec}`;
  }, 1000);
}

function pause() {
  clearInterval(currentTimeout);
}

function stop() {
  stopButton.hidden = true;
  pause();
  timeCount = 0;
  time.innerHTML = `00 : 00`;
}
<div>
  <h1 id="time">00 : 00</h1>
  <br />
  <div>
    <button onclick="play()">play</button>
    <button onclick="pause()">pause</button>
    <button onclick="stop()" id="stop" hidden>Reset</button>
  </div>
</div>


1
欢迎来到Stackoverflow。请勿仅发布代码作为答案。虽然您的解决方案可能很有用,但您还应该解释为什么这段代码可以解决问题,并且该问题已经超过8年并且已有一个被接受的答案! - MD Zand

0
对于那些对替代方案感兴趣的人(例如,我的特例是临时暂停自动轮播功能),你可以将间隔的创建放入一个函数中,并在一段时间后使用setTimeout调用它以重新启动。
var carouselindex = 0,
    carouselinterval;

function changeoffset(dir) {
    // HTML Elements
    var container = document.getElementsByClassName("container")[0],
        indicator = document.getElementsByClassName("indicator")[0],
        width = container.offsetWidth,
        items = container.childElementCount;

    // Setting up index
    if (dir === '-' && carouselindex > 0) {
        carouselindex--;
    } else if (dir === '-' && carouselindex === 0) {
        carouselindex = (items - 1);
    } else if (dir === '+' && carouselindex < (items - 1)) {
        carouselindex++;
    } else if (dir === '+' && carouselindex === (items - 1)) {
        carouselindex = 0;
    }

    // Calculating offset
    var newoffset = Math.round(carouselindex * width),
        indicatoroffset = Math.round(carouselindex * 22);

    container.scrollTo(newoffset, 0);
    indicator.style.left = indicatoroffset + "px";
}

function startcarousel() {
    carouselinterval = setInterval(function() {
        changeoffset('+');
    }, 1000);
}

function pausecarousel(dir) {
    clearInterval(carouselinterval);
    changeoffset(dir);

    setTimeout(startcarousel, 5000);
}

startcarousel();

一些重要的注释以消除任何困惑。我使用“+”或“-”表示旋转木马应该移动的方向,它们通常在变量dir中定义。

对于想知道如何启动和暂停JavaScript间隔的人来说,唯一重要的部分是changeoffset函数之外的代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接