2
\$\begingroup\$

This is my first JavaScript application (it's inspired by this timer). How could I improve it?

I had to set the setInterval() to 980ms instead of 1000ms because it was slower than a real clock for some reason.

The full app

Main JS:

var btn1 = document.getElementById('start');
var btn2 = document.getElementById('pause');
var btn3 = document.getElementById('restart')
var h1 = document.getElementById('h1');
var h1Clr = h1.style.color;
var h1Nmb = 0;
var h2 = document.getElementById('h2');
var h2Clr = h1.style.color;
var h2Nmb = 0;
var m1 = document.getElementById('m1');
var m1Clr = h1.style.color;
var m1Nmb = 0;
var m2 = document.getElementById('m2');
var m2Clr = h1.style.color;
var m2Nmb = 0;
var s1 = document.getElementById('s1');
var s1Clr = h1.style.color;
var s1Nmb = 0;
var s2 = document.getElementById('s2');
var s2Clr = h1.style.color;
var s2Nmb = 0;
window.addEventListener("keydown", function (e) {
 var keycode = String.fromCharCode(e.keyCode);
 var isNmbr = Number(keycode);
 if (s2Clr == 'white' && (isNmbr || keycode == 0)) {
 s2.textContent = String.fromCharCode(e.keyCode);
 s2.style.color = 'rgb(192, 33, 33)';
 s2Clr = 'rgb(192, 33, 33)';
 window.s2Nmb = Number(s2.textContent);
 } else if (s1Clr == 'white' && (isNmbr || keycode == 0)) {
 s1.textContent = String.fromCharCode(e.keyCode);
 s1.style.color = 'rgb(192, 33, 33)';
 s1Clr = 'rgb(192, 33, 33)';
 window.s1Nmb = Number(s1.textContent);
 } else if (m2Clr == 'white' && (isNmbr || keycode == 0)) {
 m2.textContent = String.fromCharCode(e.keyCode);
 m2.style.color = 'rgb(192, 33, 33)';
 m2Clr = 'rgb(192, 33, 33)';
 window.m2Nmb = Number(m2.textContent);
 } else if (m1Clr == 'white' && (isNmbr || keycode == 0)) {
 m1.textContent = String.fromCharCode(e.keyCode);
 m1.style.color = 'rgb(192, 33, 33)';
 m1Clr = 'rgb(192, 33, 33)';
 window.m1Nmb = Number(m1.textContent);
 } else if (h2Clr == 'white' && (isNmbr || keycode == 0)) {
 h2.textContent = String.fromCharCode(e.keyCode);
 h2.style.color = 'rgb(192, 33, 33)';
 h2Clr = 'rgb(192, 33, 33)';
 window.h2Nmb = Number(h2.textContent);
 } else if (h1Clr == 'white' && (isNmbr || keycode == 0)) {
 h1.textContent = String.fromCharCode(e.keyCode);
 h1.style.color = 'rgb(192, 33, 33)';
 h1Clr = 'rgb(192, 33, 33)';
 window.h1Nmb = Number(h1.textContent);
 }
});
var myVar;
btn1.onclick = function () {
 window.myVar = setInterval(myTimer, 980);
}
btn2.onclick = function () {
 clearInterval(window.myVar);
}
btn3.onclick = function () {
 window.location.reload();
}
function myTimer() {
 //s2
 if (s2Nmb != 0) {
 if (s2Nmb == 0) {
 s2Nmb = 10;
 };
 s2Nmb--;
 s2.textContent = s2Nmb;
 } else {
 s2Nmb = 9;
 s2.textContent = s2Nmb;
 if (s2Nmb == 0) {
 s2Nmb = 10;
 };
 }
 //s1
 if (s2Nmb == 9) {
 if (s1Nmb == 0) {
 s1Nmb = 6;
 };
 s1Nmb--;
 s1.textContent = s1Nmb;
 }
 //section Minutes
 //m2
 if (s1Nmb == 5 && s2Nmb == 9) {
 if (m2Nmb == 0) {
 m2Nmb = 10;
 };
 m2Nmb--;
 m2.textContent = m2Nmb;
 }
 //m1
 if (m2Nmb == 9 && s1Nmb == 5 && s2Nmb == 9) {
 if (m1Nmb == 0) {
 m1Nmb = 6;
 };
 m1Nmb--;
 m1.textContent = m1Nmb;
 }
 //h2
 if (m1Nmb == 5 && m2Nmb == 9 && s1Nmb == 5 && s2Nmb == 9) {
 if (h2Nmb == 0) {
 h2Nmb = 10;
 };
 h2Nmb--;
 h2.textContent = h2Nmb;
 }
 //h1
 if (h2Nmb == 9 && m1Nmb == 5 && m2Nmb == 9 && s1Nmb == 5 && s2Nmb == 9) {
 if (h1Nmb == 0) {
 h1Nmb = 6;
 };
 h1Nmb--;
 h1.textContent = h1Nmb;
 }
 //alert
 if (h1Nmb == 0 && h2Nmb == 0 && m1Nmb == 0 && m2Nmb == 0 && s1Nmb == 0 && s2Nmb == 0) {
 clearInterval(window.myVar);
 var snd = new Audio("alarm_beep.wav"); 
 snd.volume = 0.3;
 snd.play();
 }
};
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Feb 8, 2018 at 19:42
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Timer accuracy

JS timers only guarantee that at least delay milliseconds elapsed before your function is called. It does not guarantee the exact time when. What happens under the hood when you call setInterval or setTimeout is that your callback gets queued for execution. JS only picks the callback up when it has cleared the execution stack. If something is holding up the JS engine, that callback maybe called later than expected.

Timer implementation

Implementation of timers also vary. If I remember correctly, the fastest interval is never less than 4ms for regular timers. So a 0ms delay is never really 0ms. Browser vendors may slow down or stop timers entirely timers when the window is not in focus or when it detects inactivity to save battery.

Guaranteeing timer accuracy

One trick to guarantee time accuracy is to never actually rely on the timer. Instead, you look at the real time. To do that, you create a timer that runs faster than a second. On each invocation, call Date.now() and compare the timestamp.

// Implementing a timer using just the timer
var intervalInput = document.getElementById('interval')
var intervalSeconds = 0
setInterval(() => {
 intervalInput.value = ++intervalSeconds
}, 1000)
// Implementing a by computing seconds from real time since the start.
var realInput = document.getElementById('real')
var start = Date.now() 
setInterval(() => {
 // Compute how many seconds since we started
 realInput.value = Math.floor((Date.now() - start) / 1000)
}, 200)
<label>Interval seconds:</label> <input id="interval">
<label>Real seconds:</label> <input id="real">

Time representation

A naive implementation of a timer involves storing the the units (seconds, minutes, hours, etc) in separate variables, and write a cascading logic (i.e. from 59s to 0s, increments mins) to update the values. This is not really scalable once you start adding more units. It's also hard to debug since you need to step through that logic.

A better way to do this is to store time in a single value, like milliseconds, and compute the units off of it. The units are just functions of the millisecond value. A countdown timer is simply a future timestamp as start value minus the current timestamp.

const hoursInput = document.getElementById('hours')
const minutesInput = document.getElementById('minutes')
const secondsInput = document.getElementById('seconds')
const startButton = document.getElementById('start')
const pauseButton = document.getElementById('pause')
const resetButton = document.getElementById('reset')
const pad = v => `00${v}`.slice(-2)
// Functions that compute units from a millisecond value
const hours = m => Math.floor((m / 3600000) % 24)
const minutes = m => Math.floor((m / 60000) % 60)
const seconds = m => Math.floor((m / 1000) % 60)
// Render function
const render = v => {
 hoursInput.value = pad(hours(v))
 minutesInput.value = pad(minutes(v))
 secondsInput.value = pad(seconds(v))
}
// This is the value you ask from the user
const duration = 3610000
let remaining = duration
let timer = null
let end = 0
startButton.addEventListener('click', e => {
 if (timer) return
 // Get end timestamp based on current time + remaining time
 end = Date.now() + remaining
 // Render the remaining time every 16ms (approx 60fps)
 timer = setInterval(() => {
 render(end - Date.now())
 }, 16)
})
pauseButton.addEventListener('click', e => {
 // Do nothing if timer not running
 if(!timer) return
 
 // Otherwise, clear timer
 clearInterval(timer)
 timer = null
 
 // Note the remaining time
 remaining = end - Date.now()
 render(remaining)
})
resetButton.addEventListener('click', e => {
 // Do nothing if timer not running
 if(!timer) return
 
 // Otherwise, clear timer
 clearInterval(timer)
 timer = null
 
 // Reset remaining to original duration
 remaining = duration
 render(remaining)
})
render(remaining)
<input id="hours">h <input id="minutes">m <input id="seconds">s
<button type="button" id="start">Start</button>
<button type="button" id="pause">Pause</button>
<button type="button" id="reset">Reset</button>

Lastly, one minor flaw in your countdown timer is that your start button doesn't check if the timer is already running. This causes it to spawn another JS timer, doubling the decrement.

answered Feb 9, 2018 at 3:28
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.