Count Down Timer Issue

130 views Asked by At

I am creating a timed testing product in PHP. I am facing a problem in keeping the exam time properly. When the exam is started, and as long as I am in the same screen, the timer is working properly. But when I open other screens while the test is on (say for 10 minutes), and after sometime comeback to the test, the timer is showing wrong time and showing as if only 2 minutes of the test time consumed in previous 10 minutes.

Just want to know why this erratic behavior of the timer of the test when I move out of the screen while the test is on and come back after sometime.

Could anyone help me with this?

PHP CODE

<?php
/*Timer Code*/
        $dateFormat = "d F Y -- g:i a";
        $targetDate = time() + (240*60);//Change the 240 to however many minutes you want to countdown
        $actualDate = time();
        $secondsDiff = $targetDate - $actualDate;
        $remainingDay     = floor($secondsDiff/60/60/24);
        $remainingHour    = floor(($secondsDiff-($remainingDay*60*60*24))/60/60);
        $remainingMinutes = floor(($secondsDiff-($remainingDay*60*60*24)-($remainingHour*60*60))/60);
        $remainingSeconds = floor(($secondsDiff-($remainingDay*60*60*24)-($remainingHour*60*60))-($remainingMinutes*60));
        $actualDateDisplay = date($dateFormat,$actualDate);
        $targetDateDisplay = date($dateFormat,$targetDate);
?>

JavaScript

/*Timer Code*/
  var days = <?php echo $remainingDay; ?>  
  var hours = <?php echo $remainingHour; ?>  
  var minutes = <?php echo $remainingMinutes; ?>  
  var seconds = <?php echo $remainingSeconds; ?> 

function setCountDown ()
{
  seconds--;
  if (seconds < 0){
      minutes--;
      seconds = 59
  }
  if (minutes < 0){
      hours--;
      minutes = 59
  }
  if (hours < 0){
      days--;
      hours = 23
  }
  document.getElementById("remain").innerHTML = "Time "+hours+" H : "+minutes+" M : "+seconds+" S";
  SD=window.setTimeout( "setCountDown()", 1000 );
  if (hours == '0' && minutes == '00' && seconds == '00') 
  { 
      seconds = "00"; window.clearTimeout(SD);
      timeStatus = 1;
      var response = window.alert("Time is up. Press OK to continue."); // change timeout message as required         
  } } // here is the end of setCountDown
3

There are 3 answers

1
trincot On BEST ANSWER

JavaScript will not always process timer ticks when not running in the foreground, so that is why you get slow-downs.

It would be more reliable if you would:

  • Use a target deadline in JS, which you translate to the remaining time every time you handle a timer event. This way it will always relate to the actual deadline.
  • Refresh the deadline information from the server from time to time (not every second, but maybe every 5 minutes). This means you'll have to use some kind of persistence on the server-side, like a session scope variable that has the deadline with which you can return the number of seconds left.
  • Verify at the server side that a submission of answers does not happen beyond the deadline (if someone tampered with the client). Again, you would need some session scope usage for implementing this.

I will not provide the code for the second or third idea, but here is the code adapted to the first idea (which may be enough for your purposes?):

/*Timer Code*/
var totalSeconds = 240*60; //<?php echo 240*60; ?>  
var deadlineMillis = Date.now() + totalSeconds * 1000;

function setCountDown () {
    var secondsRemaining = Math.max(0, Math.round((deadlineMillis - Date.now()) / 1000)),
        minutes = Math.floor(secondsRemaining / 60),
        hours = Math.floor(minutes / 60),
        days = Math.floor(hours / 24);
    hours = hours % 24;
    minutes = ('0' + (minutes % 60)).substr(-2);
    seconds = ('0' + (secondsRemaining % 60)).substr(-2);
    document.getElementById("remain").textContent = 
        "Time "+hours+" H : "+minutes+" M : "+seconds+" S";
    if (secondsRemaining === 0) { 
        timeStatus = 1;
        alert("Time is up. Press OK to continue."); // change timeout message as required
        return;
    }
    setTimeout(setCountDown, 1000);
}

setCountDown();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="remain"></div>

Replace the 240*60 by a PHP generated value.

5
John Denver On

This works for me with the listed changes:

PHP:

<?php
/*Timer Code*/
        $dateFormat = "d F Y -- g:i a";
        $targetDate = time() + (240*60);//Change the 240 to however many minutes you want to countdown
        $actualDate = time();
        $secondsDiff = $targetDate - $actualDate;
        $remainingDay     = floor($secondsDiff/60/60/24);
        $remainingHour    = floor(($secondsDiff-($remainingDay*60*60*24))/60/60);
        $remainingMinutes = floor(($secondsDiff-($remainingDay*60*60*24)-($remainingHour*60*60))/60);
        $remainingSeconds = floor(($secondsDiff-($remainingDay*60*60*24)-($remainingHour*60*60))-($remainingMinutes*60));
        $actualDateDisplay = date($dateFormat,$actualDate);
        $targetDateDisplay = date($dateFormat,$targetDate);
?>
  • PHP opening should be

JavaScript:

/*Timer Code*/
  var days = 0;
  var hours = 4;
  var minutes = 0;
  var seconds = 0;

  setCountDown();

function setCountDown ()
{
  seconds--;
  if (seconds < 0){
      minutes--;
      seconds = 59
  }
  if (minutes < 0){
      hours--;
      minutes = 59
  }
  if (hours < 0){
      days--;
      hours = 23
  }
  if (hours == '0' && minutes == '00' && seconds == '00') 
  { 
      seconds = "00"; 
      window.clearTimeout(SD);
      timeStatus = 1;
      var response = window.alert("Time is up. Press OK to continue."); // change timeout message as required         
  } 
  console.log(days+'/'+hours+'/'+minutes+'/'+seconds);
  SD=setTimeout(setCountDown, 1000);
}
  • setCountDown was missing it's closing bracket
  • setTimeout should be called with just the function's name. Calling it with () after the name is passing the returned value into setTimeout, not the function itself. (This last point is not relevant to your question as you had the function call in quotes)
  • The re-calling of the function should really come after you check if you're at 0.

For a more accurate timer system, consider using timestamps from the server and user's computer instead of variables that can be manipulated. Then you can continuously pull the current time from the user's computer to see how much time has passed.

0
Kamal On

Most modern browser intentionally put lower priority on inactive tabs/windows. Thus the timer will be slowed down. So what you face is not erratic behavior. It is by design to increase browser performance.

https://developers.google.com/web/updates/2017/03/background_tabs#budget-based_background_timer_throttling

https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout#Timeouts_in_inactive_tabs_throttled_to_>1000ms

Solution: use web worker