Search code examples
javascriptcssanimationcountdowngsap

Countdown timer 'delays' when tab is inactive?


Trying to build a very simple Javascript countdown. However, whenever the tab is inactive, the countdown begins to lag and holds an incorrect count.

See jsfiddle here for example: 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);

}

I'm unsure whether the problem lies with the animation, or with the JS code itself.

For clarification: I want the countdown to continue when the tab is inactive, or to 'catch up with itself' when the tab comes back in to focus.

I know that setTimeout and setInterval can cause issues with inactive tabs, but I'm not entirely sure how to fix this.

Any help would be much appreciated!


Solution

  • For this you can use the HTML5 Visibility API for detecting if the browser tab is active or not. And use regular binding of event handlers for focus and blur for the browser window.

    Basically you pause() the timeline when you blur out of the tab, and then play() when you give the tab refocus. Example of this in action:

    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 Visibility Docs:

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

    Regarding GreenSock Forum Topic:

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

    Also in GSAP the equivalent for setTimeout() is 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
    }
    

    I hope this helps!