如何暂停 setInterval()函数?

如何使用 Javascript 暂停并恢复 setInterval ()函数?

例如,也许我有一个秒表告诉你的秒数,你已经看了网页。有一个“暂停”和“恢复”按钮。ClearInterval ()在这里不起作用的原因是,如果用户在第40秒和第800毫秒点击“暂停”按钮,当他点击“恢复”按钮时,经过的秒数必须在200毫秒后增加1。如果我在计时器变量上使用 clearInterval ()函数(当点击暂停按钮时) ,然后再次在计时器变量上使用 setInterval ()函数(当点击恢复按钮时) ,那么过去的秒数只会在1000毫秒后增加1,这会破坏秒表的准确性。

那我该怎么做呢?

153221 次浏览

You could use a flag to keep track of the status:

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>

This is just what I would do, I'm not sure if you can actually pause the setInterval.

Note: This system is easy and works pretty well for applications that don't require a high level of precision, but it won't consider the time elapsed in between ticks: if you click pause after half a second and later click play your time will be off by half a second.

You shouldn't measure time in interval function. Instead just save time when timer was started and measure difference when timer was stopped/paused. Use setInterval only to update displayed value. So there is no need to pause timer and you will get best possible accuracy in this way.

Why not use a simpler approach? Add a class!

Simply add a class that tells the interval not to do anything. For example: on hover.

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>

I've been looking for this fast and easy approach for ages, so I'm posting several versions to introduce as many people to it as possible.

While @Jonas Giuro is right when saying that:

You cannot PAUSE the setInterval function, you can either STOP it (clearInterval), or let it run

On the other hand this behavior can be simulated with approach @VitaliyG suggested:

You shouldn't measure time in interval function. Instead just save time when timer was started and measure difference when timer was stopped/paused. Use setInterval only to update displayed value.

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>

I know this thread is old, but this could be another solution:

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);
};

The following code, provides a precision way to pause resume a timer.

How it works:

When the timer is resumed after a pause, it generates a correction cycle using a single timeout, that will consider the pause offset (exact time when the timer was paused between cycles). After the correction cycle finishes, it schedules the following cycles with a regular setInteval, and continues normally the cycle execution.

This allows to pause/resume the timer, without losing the sync.

Code :

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">

Code source :

This Timer is a modified and simplified version of advanced-timer, a js library created by myself, with many more functionalities.

The full library and documentation is available in NPM and GITHUB

My simple way:

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()
}


How to use:

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()
}

The code is written quickly and did not refactored, raise the rating of my answer if you want me to improve the code and give ES2015 version (classes).

i wrote a simple ES6 class that may come handy. inspired by https://stackoverflow.com/a/58580918/4907364 answer

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();
}
}

usage is pretty straightforward,

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

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>