选项卡处于非活动状态时倒数计时器 'delays'?

Countdown timer 'delays' when tab is inactive?

尝试构建一个非常简单的 Javascript 倒计时。但是,只要该选项卡处于非活动状态,倒计时就会开始滞后并保持不正确的计数。

例如,请参阅此处的 jsfiddle:https://jsfiddle.net/gbx4ftcn/

function initTimer(t) {

  var self = this,
    timerEl = document.querySelector('.timer'),
    minutesGroupEl = timerEl.querySelector('.minutes-group'),
    secondsGroupEl = timerEl.querySelector('.seconds-group'),

    minutesGroup = {
      firstNum: minutesGroupEl.querySelector('.first'),
      secondNum: minutesGroupEl.querySelector('.second')
    },

    secondsGroup = {
      firstNum: secondsGroupEl.querySelector('.first'),
      secondNum: secondsGroupEl.querySelector('.second')
    };

  var time = {
    min: t.split(':')[0],
    sec: t.split(':')[1]
  };

  var timeNumbers;

  function updateTimer() {

    var timestr;
    var date = new Date();

    date.setHours(0);
    date.setMinutes(time.min);
    date.setSeconds(time.sec);

    var newDate = new Date(date.valueOf() - 1000);
    var temp = newDate.toTimeString().split(" ");
    var tempsplit = temp[0].split(':');

    time.min = tempsplit[1];
    time.sec = tempsplit[2];

    timestr = time.min + time.sec;
    timeNumbers = timestr.split('');
    updateTimerDisplay(timeNumbers);

    if (timestr === '0000')
      countdownFinished();

    if (timestr != '0000')
      setTimeout(updateTimer, 1000);

  }

  function animateNum(group, arrayValue) {

    TweenMax.killTweensOf(group.querySelector('.number-grp-wrp'));
    TweenMax.to(group.querySelector('.number-grp-wrp'), 1, {
      y: -group.querySelector('.num-' + arrayValue).offsetTop
    });

  }

  setTimeout(updateTimer, 1000);

}

我不确定问题出在动画上,还是出在 JS 代码本身。

澄清一下:我希望在选项卡处于非活动状态时继续倒计时,或者在选项卡重新聚焦时倒计时 'catch up with itself'。

我知道 setTimeoutsetInterval 可能会导致非活动选项卡出现问题,但我不确定如何解决此问题。

如有任何帮助,我们将不胜感激!

首先不得不说的是,那些倒计时的动画真是出类拔萃,大佬!干得好...

现在回答: 正如许多文章中提到的,这里是其中一篇:

setInterval(func, delay) does not guarantee a given delay between executions. There are cases when the real delay is more or less than given. In fact, it doesn’t guarantee that there be any delay at all.

来源:http://javascript.info/tutorial/settimeout-setinterval
和..

Most browsers apply performance improvements by reducing the priority of several tasks on inactive tabs or even some portions of the page that aren't on screen. Sometimes these improvements affects the executions of Javascript intervals as well.

来源:How can I make setInterval also work when a tab is inactive in Chrome?

如您所见,他们提到 setInterval 不能保证执行之间的给定延迟 即使选项卡处于活动状态 ,我们假设 setTimeout(你用的那个)也是一样的,因为它们相对来说是"the same"

那么这个问题的解决方案是什么?
好吧,你实际上可以做的是检查事件之间实际过去了多少次,像这样Codepen

编辑:正如您所要求的,这是您修复倒计时的 fiddle,我对所做的更改有 //commented到 fiddle,希望它能让您更容易理解。所以现在,当您将倒计时与其他计时器(例如您的 phone 的计时器)配对时,即使选项卡处于非活动状态或帧速率降低,您也应该得到相同的结果。

如果您喜欢我的回答,请考虑将其标记为 "the answer" 或至少投赞成票 ;)

当用户离开选项卡然后 returns 确保计时器保持正确的最简单方法是使用会话存储 - 存储原始开始时间:

在第一次加载时 - 获取本地当前日期时间并存储到会话存储中(请注意,它将只接受一个字符串 - 因此您必须将值字符串化,然后在检索时再次解析它)。

当tab失去焦点时,设置的开始时间在ss中仍会保存为startTime。 When the tab regains focus - have a function that gets the new current datetime, gets the stored datedtime from session storage and calculates the difference.然后计时器可以更新为新的减少时间。

例如,如果它的 10:00 在页面加载时 - 将 startTime 设置为 ss。然后用户在网站上停留 1 分钟,离开网站 5 分钟。返回时 - 上述过程将确定当前时间是 10:06 并确定它比开始时间晚 6 分钟,因此可以更新计时器。

这避免了对间隔和计时器等的需要。如果需要,您可以将特异性降低到毫秒。还有 - 我也喜欢你的 fiddle。

为此,您可以使用 HTML5 Visibility API 来检测浏览器选项卡是否处于活动状态。并为浏览器使用焦点和模糊事件处理程序的常规绑定window。

基本上,您 pause() 模糊选项卡时的时间轴,然后 play() 重新聚焦选项卡时。实际操作示例:

http://codepen.io/jonathan/pen/sxgJl

// Set the name of the hidden property and the change event for visibility
var hidden, visibilityChange; 
if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support 
   hidden = "hidden";
   visibilityChange = "visibilitychange";
} else if (typeof document.mozHidden !== "undefined") {
    hidden = "mozHidden";
    visibilityChange = "mozvisibilitychange";
} else if (typeof document.msHidden !== "undefined") {
    hidden = "msHidden";
    visibilityChange = "msvisibilitychange";
} else if (typeof document.webkitHidden !== "undefined") {
    hidden = "webkitHidden";
    visibilityChange = "webkitvisibilitychange";
}

// If the page is hidden, pause the video;
// if the page is shown, play the video
function handleVisibilityChange() {
   if (document[hidden]) {
      tl.pause();
   } else {
      tl.play();
   }
}

// Warn if the browser doesn't support addEventListener or the Page Visibility API
if (typeof document.addEventListener === "undefined" || typeof document[hidden] === "undefined") {
   // do nothing or throw error via alert()
   alert("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.");
} else {
   // Handle page visibility change 
   // Pause timeline  
   tl.pause();
}

HTML5 可见性文档:

https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API

关于 GreenSock 论坛主题:

http://forums.greensock.com/topic/9059-cross-browser-to-detect-tab-or-window-is-active-so-animations-stay-in-sync-using-html5-visibility-api/

同样在 GSAP 中,setTimeout() 的等价物是 delayedCall()

Provides a simple way to call a function after a set amount of time (or frames). You can optionally pass any number of parameters to the function too.

GSAP delayedCall(): http://greensock.com/docs/#/HTML5/GSAP/TweenMax/delayedCall/

//calls myFunction after 1 second and passes 2 parameters:
TweenMax.delayedCall(1, myFunction, ["param1", "param2"]);

function myFunction(param1, param2) {
    //do stuff
}

希望对您有所帮助!

您可以使用此代码。每次选项卡更改时,它都会重新计算结束时间并更新计数器。

let interval;
let duration=timeDifference(endTime(),nowDate())
updateTime();
$(window).focus(function () {
    clearInterval(interval)
    updateTime();
});

function updateTime() {
     interval = setInterval(function () {
        var timer = duration.split(':');
        //by parsing integer, I avoid all extra string processing
        var hours = parseInt(timer[0], 10);
        var minutes = parseInt(timer[1], 10);
        var seconds = parseInt(timer[2], 10);
        --seconds;
        minutes = (seconds < 0) ? --minutes : minutes;
        if (minutes < 0) clearInterval(interval);
        seconds = (seconds < 0) ? 59 : seconds;
        seconds = (seconds < 10) ? '0' + seconds : seconds;
        hours = (hours < 10) ? '0' + hours : hours;
        //minutes = (minutes < 10) ?  minutes : minutes;
        $('.countdown').html(hours + ':' + minutes + ':' + seconds);
         duration = hours + ':' + minutes + ':' + seconds;
    }, 1000);
}
function nowDate() {
    let date = new Date()
    let time1 = new Date();
    date = date.toISOString().slice(0, 10);
    date = date.split('-');
    return new Date(date[2] + '/' + date[1] + '/' + date[0] + " " + time1.getHours() + ":" + time1.getMinutes() + ":" + time1.getSeconds());
}
function endTime() {
    let endTime = $('input[name=end_date]').val();
    endTime = endTime.split(' ');
    let date = endTime[0].split('-');
    let time = endTime[1];
    return new Date(date[2] + '/' + date[1] + '/' + date[0] + " " + time);
}
function timeDifference(date1,date2) {
    var difference = date1.getTime() - date2.getTime();

    var daysDifference = Math.floor(difference/1000/60/60/24);
    difference -= daysDifference*1000*60*60*24

    var hoursDifference = Math.floor(difference/1000/60/60);
    difference -= hoursDifference*1000*60*60

    var minutesDifference = Math.floor(difference/1000/60);
    difference -= minutesDifference*1000*60

    var secondsDifference = Math.floor(difference/1000);

    return hoursDifference +":"+minutesDifference+":"+secondsDifference
}