如何暂停 setInterval() 函数?

Posted

技术标签:

【中文标题】如何暂停 setInterval() 函数?【英文标题】:How can I pause setInterval() functions? 【发布时间】:2014-02-12 05:08:18 【问题描述】:

如何使用 javascript 暂停和恢复 setInterval() 函数?

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

那我该怎么做呢?

【问题讨论】:

Code for a simple JavaScript countdown timer? 的可能重复项 How do I pause a windows.setInterval in javascript?的可能重复 看看上面提到的非常相似的问题中的this answer。 【参考方案1】:

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

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。

注意:这个系统很简单,非常适合不需要高精度的应用程序,但它不会考虑滴答之间经过的时间:如果你在半秒后点击暂停,然后再点击玩你的时间会少半秒。

【讨论】:

如果你有多个click.pause.play,它最终会out run by the time.,你无法预测n/1000 部分将是click。因此损失n/1000 Sec 或获得(1000-n)/1000 sec。但在某些情况下,这没有意义,但在某些令人叹为观止的情况下,它们会造成很大的伤害。 无论如何我喜欢这个想法(简单而干净),我将它与我的代码一起使用,其中没有杀戮。 This comment is just to notify those who are going to kill some one with their script 不错的把戏。感谢您的回答,但您没有暂停 setInterval 函数。 你不能暂停 setInterval 函数,你可以停止它(clearInterval),或者让它运行。我提供的解决方案是针对 OP 问题的解决方案,我并没有声称它适用于所有可能的情况。 这并没有解决他的问题。结果仍然是概率性的。更好的选择是清除间隔,并增加自上次火灾以来经过的时间。间隔事件可能会增加 1,暂停事件可能会增加 0.42。但是您还需要存储每个间隔触发的时间,以便与它进行比较。 Date.now() 是你想要的函数。【参考方案2】:

您不应该在区间函数中测量时间。相反,只需在计时器启动时节省时间,并在计时器停止/暂停时测量差异。仅使用 setInterval 更新显示的值。因此无需暂停计时器,您将通过这种方式获得尽可能高的准确性。

【讨论】:

为什么人们不应该在间隔中测量时间?如果他们想显示实时计时器/倒计时,那么无论如何他们都需要重新计算经过的持续时间,为什么不在那里跟踪呢?【参考方案3】:

虽然@Jonas Giuro 说得对:

你不能暂停 setInterval 函数,你可以停止它(clearInterval),或者让它运行

另一方面,这种行为可以用approach @VitaliyG suggested模拟:

您不应该在区间函数中测量时间。相反,只需在计时器启动时节省时间,并在计时器停止/暂停时测量差异。仅使用 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>

【讨论】:

【参考方案4】:

为什么不使用更简单的方法呢?添加课程!

只需添加一个告诉间隔不要做任何事情的类。例如:悬停时。

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版本(类),请提高我的回答评分。

【讨论】:

【参考方案6】:

我编写了一个简单的 ES6 类,它可能会派上用场。 灵感来自https://***.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();

【讨论】:

谢谢您,但请注意,您的代码中有私有/公共修饰符来自 TypeScript,而不是 ES6 的一部分【参考方案7】:

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

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

【讨论】:

【参考方案8】:

以下代码提供了一种暂停恢复计时器的精确方法。

工作原理:

当定时器暂停后恢复时,它会使用单个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的修改简化版,功能更多。

完整的库和文档在NPM 和GITHUB 中提供

【讨论】:

以上是关于如何暂停 setInterval() 函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 setInterval 函数中播放声音?

在 React Native Android 和 iOS 中使用 Set Interval

使用setInterval通过JQuery暂停幻灯片放映吗?

JS数据驱动的定时器开关(可暂停)

jquery html JS setInterval暂停和重启

地球上最准确的视频暂停(currentTime Vs mediaTime 和 requestVideoFrameCallback Vs setInterval)