【问题标题】:Countdown timer 'delays' when tab is inactive?选项卡处于非活动状态时倒数计时器“延迟”?
【发布时间】:2017-02-10 00:03:09
【问题描述】:

尝试构建一个非常简单的 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 代码本身。

为了澄清:我希望在选项卡处于非活动状态时继续倒计时,或者在选项卡重新聚焦时“赶上自己”。

我知道setTimeoutsetInterval 会导致非活动标签出现问题,但我不完全确定如何解决此问题。

任何帮助将不胜感激!

【问题讨论】:

  • 请删除任何与特定问题无关的代码
  • 提示:将结束时间存储为日期,并始终根据现在和结束之间的差异进行计算
  • @alexwc_ 该链接是相关的,但没有为这种情况提供解决方案,所以它不是真正的重复
  • 如果可以使用 service worker,请使用 Task scheduler

标签: javascript css animation countdown gsap


【解决方案1】:

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

基本上你 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()

提供了一种在设定的时间(或帧)后调用函数的简单方法。您也可以选择将任意数量的参数传递给函数。

GSAP 延迟调用():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
}

我希望这会有所帮助!

【讨论】:

  • 谢谢,乔纳森 ;)
【解决方案2】:

首先,我不得不提一下,那些倒计时动画真的很出色,很优雅亲爱的先生!干得好……

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

setInterval(func, delay) 不保证给定的延迟 处决。在某些情况下,实际延迟大于或小于 给定的。事实上,它并不能保证有任何延迟。

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

大多数浏览器通过降低优先级来提高性能 非活动选项卡上的几个任务,甚至页面的某些部分 不在屏幕上。有时这些改进会影响 Javascript 间隔的执行也是如此。

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

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

那么解决这个问题的方法是什么?
好吧,您实际上可以做的是检查事件之间实际经过了多少次,例如Codepen

编辑:根据您的要求,这是您的倒计时的fiddle 修复,我有//commented 对我对小提琴所做的更改,所以它有望使你更容易理解。所以现在,当您将倒计时与其他计时器(例如您手机的计时器)配对时,即使选项卡处于非活动状态或帧速率减慢,您也应该获得相同的结果。

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

【讨论】:

  • 感谢您的美言!这超级有趣。你知道我会在哪里实现像我这样的脚本吗?我对 JS 很陌生!
  • 这非常有帮助,再次感谢您!虽然,它并不总是有效。有时计时器达到 0000,会响起并显示重新加载按钮,就像它应该的那样。其他时候,它不会响铃和循环,从 60:00 开始倒计时。
  • 嗯,我真的很抱歉,但是,在我尝试了大约 5 次小提琴之后,我没有看到你描述的错误。另外,如果您对我的回答感到满意,请单击我的回答旁边的复选标记将其标记为“答案”!真的很感激..
【解决方案3】:

当用户离开标签然后返回时,确保计时器保持正确的最简单方法是使用会话存储 - 存储原始开始时间:

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

当标签丢失焦点时,设置开始时间仍将存储为SS的起始时间。当标签恢复焦点时 - 具有获取新当前日期时间的函数,从会话存储获取存储的日期时间,并计算差异。然后可以将计时器更新为新的减少时间。

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

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

【讨论】:

    【解决方案4】:

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

    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
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-26
      • 1970-01-01
      • 2016-05-30
      • 2021-05-25
      • 2022-06-14
      • 2019-08-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多