skip to Main Content

I have a JavaScript stopwatch on my website that my authors have to use it when they are doing some actions. This actions are depend on using another pages/tabs on my website but currently when my authors going to another tab, the stopwatch will pause and when they return to the page, stopwatch will continue. It is supposed to continue even if they change tabs.

Here is my code:

let startBtn = document.getElementById('start');
let stopBtn = document.getElementById('stop');
let resetBtn = document.getElementById('reset');
let timeY = document.getElementById('timey');

let minute = 00;
let second = 00;
let count = 00;

window.onload = function() {
  document.getElementById('stop').style.display = 'none';
};

startBtn.addEventListener('click', function () {
    timer = true;
    stopWatch();
    
    document.getElementById('stop').style.display = 'inline';
    document.getElementById('start').style.display = 'none';
});

stopBtn.addEventListener('click', function () {
    timer = false;
    document.getElementById('start').innerHTML = "Continue";
    document.getElementById('stop').style.display = 'none';
    document.getElementById('start').style.display = 'inline';
});

resetBtn.addEventListener('click', function () {
    timer = false;

    minute = 0;
    second = 0;
    count = 0;

    document.getElementById('min').innerHTML = "00";
    document.getElementById('sec').innerHTML = "00";
    document.getElementById('count').innerHTML = "00";
    
    document.getElementById('acf-field_651d01208c082').value = "00:00:00";
});

function stopWatch() {
    if (timer) {
        count++;

        if (count == 100) {
            second++;
            count = 0;
        }

        if (second == 60) {
            minute++;
            second = 0;
        }




        let minString = minute;
        let secString = second;
        let countString = count;



        if (minute < 10) {
            minString = "0" + minString;
        }

        if (second < 10) {
            secString = "0" + secString;
        }

        if (count < 10) {
            countString = "0" + countString;
        }

        document.getElementById('min').innerHTML = minString;
        document.getElementById('sec').innerHTML = secString;
        document.getElementById('count').innerHTML = countString;
        
        document.getElementById('acf-field_651d01208c082').value = minString+":"+secString+":"+countString;
        
        
        setTimeout(stopWatch, 10);
    }
}

jQuery('#acf-group_651d0120348fc').insertAfter('#submitdiv');
#acf-field_651d01208c082 {cursor: help;text-align: center;font-family: Vazirmatn UI, sans-serif !important;border: 0px;font-size: 19px;letter-spacing: 4px;background: transparent !important;background: #fff0 !important;}
#buttons #start {background: #4CAF50;color: #FFF;border: 1px solid #4CAF50;}
#buttons #start:hover {background: #409743;color: #FFF;border: 1px solid #4CAF50;}
#buttons #stop {display:none;background: #e73e77;color: #FFF;border: 1px solid #e73e77;}
#buttons #stop:hover {background: #c13b68;color: #FFF;border: 1px solid #e73e77;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="timey" style="text-align: center;direction: ltr !important;font-size: 0px !important;font-size: 0.01px !important;visibility: hidden;">
            <span class="digit" id="min">
                00</span>
            <span class="txt">:</span>
            <span class="digit" id="sec">
                00</span>
            <span class="txt">:</span>
            <span class="digit" id="count">
                00</span>
        </div>
        <div id="buttons" style="text-align: center;">
            <button type="button" class="button" id="start">start</button>
            <button type="button" class="button" id="stop" style="display: none;">stop</button>
            <button type="button" class="button" id="reset">reset</button>
        </div>

<input type="text" id="acf-field_651d01208c082" name="acf[field_651d01208c082]" placeholder="00:00:00" readonly="">

2

Answers


  1. To add the ability to make the stopwatch appear to continue progressing rather than pausing even if the user moves to a different tab, you can leverage the web browser’s visibilitychange event.

    This is the code adding the visibilitychange event to the code:

    let startBtn = document.getElementById('start');
    let stopBtn = document.getElementById('stop');
    let resetBtn = document.getElementById('reset');
    let timeY = document.getElementById('timey');
    
    let minute = 00;
    let second = 00;
    let count = 00;
    
    // Add
    let startDate = new Date();
    
    window.onload = function() {
        document.getElementById('stop').style.display = 'none';
    };
    
    startBtn.addEventListener('click', function () {
        timer = true;
        stopWatch();
    
        document.getElementById('stop').style.display = 'inline';
        document.getElementById('start').style.display = 'none';
    });
    
    stopBtn.addEventListener('click', function () {
        timer = false;
        document.getElementById('start').innerHTML = "Continue";
        document.getElementById('stop').style.display = 'none';
        document.getElementById('start').style.display = 'inline';
    });
    
    resetBtn.addEventListener('click', function () {
        timer = false;
    
        minute = 0;
        second = 0;
        count = 0;
    
        document.getElementById('min').innerHTML = "00";
        document.getElementById('sec').innerHTML = "00";
        document.getElementById('count').innerHTML = "00";
    
        document.getElementById('acf-field_651d01208c082').value = "00:00:00";
    });
    
    function timeCalculation() {
        let nowDate = new Date();
        nowDate.setMinutes(nowDate.getMinutes() - startDate.getMinutes());
        nowDate.setSeconds(nowDate.getSeconds() - startDate.getSeconds());
        nowDate.setMilliseconds(nowDate.getMilliseconds() - startDate.getMilliseconds());
    
        let minute = nowDate.getMinutes();
        let second = nowDate.getSeconds();
        let count = parseInt(nowDate.getMilliseconds() / 10);
        
        return [minute, second, count];
    }
    
    function stopWatch() {
        if (timer) {
            count++;
    
            if (count == 100) {
                second++;
                count = 0;
            }
    
            if (second == 60) {
                minute++;
                second = 0;
            }
    
    
            let minString = minute;
            let secString = second;
            let countString = count;
    
    
            if (minute < 10) {
                minString = "0" + minString;
            }
    
            if (second < 10) {
                secString = "0" + secString;
            }
    
            if (count < 10) {
                countString = "0" + countString;
            }
    
            document.getElementById('min').innerHTML = minString;
            document.getElementById('sec').innerHTML = secString;
            document.getElementById('count').innerHTML = countString;
        
            document.getElementById('acf-field_651d01208c082').value = minString+":"+secString+":"+countString;
        
        
            setTimeout(stopWatch, 10);
        }
    }
    
    // Add
    document.addEventListener("visibilitychange", handleVisibilityChange);
    
    function handleVisibilityChange() {
        if (document.visibilityState === "visible") { 
            if (timer) { 
                [minute, second, count] = timeCalculation();
            } else {
                startDate = new Date();
            }
        }
    }
    
    jQuery('#acf-group_651d0120348fc').insertAfter('#submitdiv');
    #acf-field_651d01208c082 {cursor: help;text-align: center;font-family: Vazirmatn UI, sans-serif !important;border: 0px;font-size: 19px;letter-spacing: 4px;background: transparent !important;background: #fff0 !important;}
    #buttons #start {background: #4CAF50;color: #FFF;border: 1px solid #4CAF50;}
    #buttons #start:hover {background: #409743;color: #FFF;border: 1px solid #4CAF50;}
    #buttons #stop {display:none;background: #e73e77;color: #FFF;border: 1px solid #e73e77;}
    #buttons #stop:hover {background: #c13b68;color: #FFF;border: 1px solid #e73e77;}
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id="timey" style="text-align: center;direction: ltr !important;font-size: 0px !important;font-size: 0.01px !important;visibility: hidden;">
                <span class="digit" id="min">
                    00</span>
                <span class="txt">:</span>
                <span class="digit" id="sec">
                    00</span>
                <span class="txt">:</span>
                <span class="digit" id="count">
                    00</span>
            </div>
            <div id="buttons" style="text-align: center;">
                <button type="button" class="button" id="start">start</button>
                <button type="button" class="button" id="stop" style="display: none;">stop</button>
                <button type="button" class="button" id="reset">reset</button>
            </div>
    
    <input type="text" id="acf-field_651d01208c082" name="acf[field_651d01208c082]" placeholder="00:00:00" readonly="">
    Login or Signup to reply.
  2. Here is a rudimentary example.

    past accumulate all the past measured interval (the one for which we have, in the past, since last reset, started and then stopped the watch).
    startTime is the starting time of the current measurement (the date at which we hit ‘start’ button the last time

    So start button just keeps the value of startTime.
    And stop button (same, when running) add now-startTime to the past accumulator.

    That’s all that is actively done to measure time. Rest (the set interval) is unnecessary and just for rendering (time is measured whether we show it or not).

    So, what that setIterval function does is just display the past accumulator, plus, if needed, the current interval as it would be if we were hitting stop now.

    let $start=document.getElementById('startstop');
    let $reset=document.getElementById('reset');
    let $watch=document.getElementById('watch');
    
    let startTime=false;
    let running=false;
    let past=0;
    
    $start.addEventListener('click', function(){
      if(running){
        running=false;
        $start.textContent='Start';
        let now=new Date();
        past += now-startTime;
      }else{
        running=true;
        $start.textContent='Stop';
        startTime=new Date();
      }
    });
    
    $reset.addEventListener('click', function(){
      startTime=new Date();
      past=0;
    });
    
    setInterval(function(){
      let total=past;
      if(running){
        total += (new Date())-startTime;
      }
      let h=Math.floor(total/3600000);
      total-=h*3600000;
      let m=Math.floor(total/60000);
      total-=m*60000;
      $watch.textContent=h+':'+m.toFixed(0).padStart(2,'0')+':'+(total/1000).toFixed(2).padStart(5,'0');
    }, 70);
    <div id=watch>0</div>
    <button id=startstop>Start</button>
    <button id=reset>Reset</button>

    As for "why 70" for the interval… Well, I could have used 10. But, I want to illustrate that this is just rendering. And you don’t really need a 100 fps rendering for a stop watch. You eyes are not that accurate. 100 ms, that is 10 fps would be enough. But since we show 2 decimal places, that would lead, to the decimal place to be almost constant (almost, because setInterval is not exactly accurate).

    We don’t really want that last decimal place to increase every 1/100th of second. We just want it to keep moving really fast, so our eye just see it moving. But we don’t want it to appear fixed neither. So not a factor of 100ms. Hence any interval small enough to create running feeling (70 is almost 15 fps) and that does not end by 00 (7 is prime. So last decimal place will show all digits). Well that are some overthinking probably. But my point is: this is only about rendering the time. Whatever interval you choose will not impact how time is measured. If you put ‘86400000’ in there, well, time is still measured as accurately, even if the display is updated only once a day. If you put ’10’, you are probably spending too much of your cpu time (or rather, your users cpu time) in rendering digits that nobody can possibly read that fast, but still, it doesn’t impact how time is measured.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search