Stopwatch

Simple stopwatch that can start delayed, in order to time something in other browser tabs/windows

by Larph

HTML

<div id="stopwatch">
	<div id="hrs" class="sw-component"></div>:<div id="min" class="sw-component"></div>:<div id="sec" class="sw-component"></div>.<div id="mil" class="sw-component"></div>
</div>

<ul id="buttons">
	<li><button onclick="startTimer()">Start</button></li>
	<li><button onclick="stopTimer()">Stop</button></li>
	<li><button onclick="resetTimer()">Reset</button></li>
	<li><label for="delayed" title="Start timer delayed, i.e. when you click Start, the timer won't actually start until you alt-tab/activate another window or switch to another tab on your browser or minimize this browser window.&#x0A;This is, so you can go to another window and quickly activate something that needs to be timed!">Delayed<input type="checkbox" id="delayed" /></label></li>
</ul>

CSS

body
{
  background: #202020;
  color: white;
  margin: 0;
  padding: 0;
}
button
{
  width: 100px;
  height: 30px;
  border-width: 1px;
  border-style: solid;
  border-color: #fff #333 #333 #fff;
  border-radius: 50px;
  background: #444;
  color: white;
  cursor: pointer;
  outline: none;
}
button:active
{
  background: #333;
  border-color: #333 #fff #fff #333;
}
.sw-component
{
  display:inline-block;
  font-family:Lucida Console;
}
#stopwatch
{
  font-size: 34pt;
  position: absolute;
  top: 40%; left: 50%;
  transform: translate(-50%, -55%);
}
#buttons
{
  position: absolute;
  top: 55%;
  left: 30%;
  transform: translate(-23%, -45%);
}
#buttons li
{
  display: inline;
  padding-left: 10px;
}

JavaScript

const HRS = document.getElementById('hrs'), MIN = document.getElementById('min'), SEC = document.getElementById('sec'), MIL = document.getElementById('mil'), DEL = document.getElementById('delayed');

var hrs = min = sec = mil = dt = ps = pt = tt = t = 0, running = started = false, iID;

function timerCycle()
{
	if (running)
	{
		t = performance.now() * 0.001;
		dt += t - pt;
		pt = t;
		tt = Math.floor(dt);
		mil = dt - tt;
		MIL.textContent = (mil).toFixed(3).slice(-3);
		sec = tt%60;
		if ( sec == ps ) return;
		ps  = sec;
		min = Math.floor(tt/60)%60;
		hrs = Math.floor(tt/3600);
		HRS.textContent = ('0'+hrs).slice(-2);
		MIN.textContent = ('0'+min).slice(-2);
		SEC.textContent = ('0'+sec).slice(-2);
	}
}

function stop()
{
	if ( iID )
	{
		clearInterval(iID);
		iID = 0;
	}
}

function start()
{
	if ( started && !running )
	{
		running = true;
		t = pt = performance.now() * 0.001;
		iID = setInterval(timerCycle, 33);
	}
}

function startTimer()
{
	if ( !started )
	{
		started = true;
		stop();
		if ( !DEL.checked )
		{
			start();
		}
	}
}

function stopTimer()
{
	if ( started )
	{
		started = running = false;
		stop();
	}
}

function resetTimer()
{
 	if ( !started )
	{
		running = false;
		dt = ps = 0;
		HRS.textContent = '00';
		MIN.textContent = '00';
		SEC.textContent = '00';
		MIL.textContent = '000';
	}
}

function onVisibilityChange()
{
	if ( started )
	{
		start();
	}
}

resetTimer();
document.addEventListener('visibilitychange', onVisibilityChange);
window.addEventListener('blur', onVisibilityChange);