如何使用 Javascript 暂停和恢复 setInterval() 函数?
例如,也许我有一个秒表可以告诉你你浏览网页的秒数。有一个“暂停”和“恢复”按钮。 clearInterval()在这里不起作用的原因是因为如果用户在第40秒和第800毫秒点击“暂停”按钮,当他点击“恢复”按钮时,经过的秒数必须增加200 毫秒后为 1。如果我在计时器变量上使用clearInterval()函数(当单击暂停按钮时),然后再次在计时器变量上使用setInterval()函数(当单击恢复按钮时),经过的秒数将增加1 仅在 1000 毫秒后,这破坏了秒表的准确性。
那么我该怎么做呢?
您可以使用标志来跟踪状态:
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。
注意:这个系统很简单,对于不需要高精度的应用程序来说效果很好,但它不会考虑滴答之间经过的时间:如果您在半秒后单击暂停,然后单击播放您的时间将缩短半秒。
您不应该在间隔函数中测量时间。相反,只需在计时器启动时节省时间并在计时器停止/暂停时测量差异。仅使用 setInterval 来更新显示值。因此无需暂停计时器,您将通过这种方式获得最佳的准确性。
虽然@Jonas Giuro 说得对:
您无法暂停 setInterval 函数,您可以停止它(clearInterval),或者让它运行
另一方面,可以使用 @VitaliyG 建议的方法来模拟这种行为:
您不应该在间隔函数中测量时间。相反,只需在计时器启动时节省时间并在计时器停止/暂停时测量差异。仅使用 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>
我写了一个简单的 ES6 类,可能会派上用场。 灵感来自https://stackoverflow.com/a/58580918/4907364答案
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();
只需添加一个类来告诉间隔不要执行任何操作。例如:悬停时。
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"> </p>
<button id="pauseInterval">Pause</button></p>
我多年来一直在寻找这种快速且简单的方法,因此我发布了多个版本以向尽可能多的人介绍它。
我知道这个线程很旧,但这可能是另一个解决方案:
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);
};
我的简单方法:
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版本(类),请提高我的答案的评级。
以下代码提供了一种精确的方法来暂停和恢复计时器。
工作原理:
当计时器在暂停后恢复时,它会使用单个timeout
生成一个
校正周期,这将考虑暂停偏移(计时器在周期之间暂停的确切时间)。校正周期结束后,按规律
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">
代码来源:
这个Timer是我自己创建的一个js库
advanced-timer
的修改和简化版本,有更多的功能。
中找到
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>
对于那些对替代方案感兴趣的人(例如,我的边缘情况是自动轮播功能的暂时暂停)。
您可以将间隔的创建放入一个函数中,并在一段时间后调用它以使用 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 Intervals 的人来说,唯一重要的部分是changeoffset 函数之外的代码。
setInterval
包装器的另一个变体,在透明和一些测试演示上具有自删除功能。所有详细信息都在代码注释中。
// Function to set interval and controls
// Accepts fn to run and interval in ms
let Interval = (fn, ms) => {
// Set flag
let isPaused = false;
// Set interval
const interval = setInterval(() => !isPaused && fn(), ms);
// Set internal methods
Interval.pause = () => isPaused = true;
Interval.resume = () => isPaused = false;
Interval.isPaused = () => isPaused;
// Set clear and self delete
Interval.stop = () => {
// Stop interval loop
clearInterval(interval);
// Check if self delete is possible
// (Interval = const or let ?)
try {
Interval = undefined;
} catch(err) {
// Here you can print some warning
// or leave muted
}
}
};
//
// Set interval
Interval(() => {
console.log(new Date().toString());
}, 1000);
//
// TESTS
//
// Let's conduct some tests below
// Timeout helper function
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
(async () => {
//
await timeout(3500);
// Pause interval after 3,5 sec
Interval.pause();
//
await timeout(3000);
// Check pause state after +3 sec
console.log(Interval.isPaused() ? `Interval is paused` : `Interval running`);
//
await timeout(3000);
// Resume interval after +3 sec
Interval.resume();
//
await timeout(3000);
// Stop interval after +3 sec
Interval.stop();
console.log(`Interval stopped`);
//
await timeout(1000);
// Check Interval internal method after +1 sec
try {
Interval.resume();
} catch(err) {
console.log(err.toString());
}
})();