Count down timer with increment - javascript

I have a switch and a timer. The switch just changes values between 0 and 1 and the timer is an interval every 5 seconds. When the interval is hit, the switch value changes (if it was 0 now it's 1, if it was 1 now it's 0) and I also have a button which forces the switch value to change and resets the timer.
I also have another timer which is running at 5ms interval and that's just to display how much time is left on the 5 second interval.
Now this is where I'm stuck. I have a button which is supposed to increment that time left by two seconds. So if three seconds have passed since the last switch, and I hit increment, now we're at six seconds until the next switch..
And that part has just been confusing me on how I should go about doing it.
Here's my html and javascript code
window.addEventListener('DOMContentLoaded', (event) => {
let timer = document.getElementById("timer");
let switchvalue = document.getElementById("switchvalue");
let force = document.getElementById("force");
let increment = document.getElementById("increment");
let boolvalue = false;
let maxtime = 5000;
var tick = Date.now();
var switchinterval = setInterval(timecounter,maxtime);
function update(){
tick = Date.now();
boolvalue = !boolvalue;
switchvalue.textContent = boolvalue;
clearInterval(switchinterval);
switchinterval = setInterval(timecounter,maxtime);
}
function timecounter(){
update();
}
let displayinterval = setInterval(() => {
let now = Date.now();
let elapsed = now-tick;
timer.textContent = maxtime-elapsed;
}, 5);
force.addEventListener("click",e=>{
update();
});
increment.addEventListener("click",e=>{
//What do I do here?
});
});
<html>
<head>
<script src="timer.js"></script>
</head>
<body>
<div id="timer">10</div>
<div id="switchvalue">false</div>
<button id="force">Switch</button>
<button id="increment">Increment</button>
</body>
</html>
I'm not sure how to make that increment button function. It seems like a simple enough problem to solve..but my brains not working

If I understood your question correctly, your increment button should simply add two seconds, right?
increment.onclick =e=>
{
tick += 2000 // add 2s
}
const timer = document.getElementById("timer")
, switchvalue = document.getElementById("switchvalue")
, force = document.getElementById("force")
, increment = document.getElementById("increment")
;
let boolvalue = false
, maxtime = 5000
, tick = Date.now()
, switchinterval
, displayinterval
;
switchinterval = setInterval(timecounter, maxtime)
;
function update()
{
tick = Date.now()
boolvalue = !boolvalue
switchvalue.textContent = boolvalue
clearInterval(switchinterval)
switchinterval = setInterval(timecounter, maxtime)
}
function timecounter()
{
update()
}
displayinterval = setInterval(_=>
{
let now = Date.now()
, elapsed = now - tick
timer.textContent = maxtime - elapsed
}, 5)
force.onclick =e=>
{
update()
}
increment.onclick =e=>
{
tick += 2000 // add 2s
}
<div id="timer">?</div>
<div id="switchvalue">false</div>
<button id="force">Switch</button>
<button id="increment">Increment</button>
But you forgot to take into account that the delay indicated in setInterval is not reliable, it represents just a hypothetical delay according to the availability of the system, most often (always) it is late.
If you want to have a reliable delay, you must use the value of the system clock to check the elapsed delay.
Which is precisely the case study of your exercise, and which has the merit of using only one setInterval to do everything:
const timer = document.getElementById("timer")
, switchvalue = document.getElementById("switchvalue")
, force = document.getElementById("force")
, increment = document.getElementById("increment")
;
let boolvalue = false
, maxtime = 5000
, timeTarget = Date.now() + maxtime
, interval_5ms
;
const timeSwich =_=>
{
switchvalue.textContent = boolvalue = !boolvalue
timeTarget = Date.now() + maxtime
}
interval_5ms = setInterval(_=>
{
let tim = timeTarget - Date.now()
if ( tim<=0 )
{
timeSwich()
tim = maxtime
}
timer.textContent = tim
}, 5)
force.onclick = timeSwich
increment.onclick =_=>
{
timeTarget = Math.min((timeTarget +2000), Date.now() + maxtime)
// timeTarget += 2000 // add 2s
}
<div id="timer">?</div>
<div id="switchvalue">false</div>
<button id="force">Switch</button>
<button id="increment">Increment</button>

Related

Why is my interval calling my function even though its in a variable?

I have a stopwatch that has buttons to start, stop, and reset the time. I assigned an interval to a variable to pause it using clearInterval every time I click the stop button, but my interval is calling the function even though I clicked no button. How do I fix this?
const startButton = document.getElementById('start');
const stopButton = document.getElementById('stop');
const resetButton = document.getElementById('reset');
const myInterval = setInterval(setTime, 10);
startButton.addEventListener('click', () => {
setInterval(setTime, 10);
})
stopButton.addEventListener('click', ()=> {
clearInterval(myInterval);
})
const timeUnits = ['00', '00', '00', '00'];
milliSeconds = 0;
seconds = 0;
minutes = 0;
hours = 0;
function setTime() {
if (minutes == 60) {
hours++;
minutes = 0;
timeUnits[0] = hours;
timeUnits[1] = 0;
} else if (seconds == 60) {
minutes++;
seconds = 0;
timeUnits[1] = minutes;
timeUnits[3] = 0;
} else if (milliSeconds == 100) {
seconds++;
milliSeconds = 0;
timeUnits[2] = seconds
timeUnits[3] = 0;
document.getElementById('para').innerHTML = timeUnits.join(':');
} else {
milliSeconds++;
timeUnits[3] = milliSeconds;
document.getElementById('para').innerHTML = timeUnits.join(':');
};
}
<p id="para">00:00:00:00</p>
<button id="start">Start</button>
<button id="stop">Stop</button>
<button id="reset">Reset</button>
You set up the interval in two places:
const myInterval = setInterval(setTime, 10); // <-- Here (A)
startButton.addEventListener('click', () => {
setInterval(setTime, 10); // <-- and here (B)
})
In spot A (auto-start on page load) you save the interval ID into myInterval so that you can later do clearInterval(myInterval).
However, in spot B (on-demand start on "start" button click), you do not do that, so the interval set by clicking the "start" button can never be cleared.
This can be fixed by saving the interval into myInterval in both places:
let myInterval = setInterval(setTime, 10)
startButton.addEventListener('click', () => {
myInterval = setInterval(setTime, 10)
})
Now, this created a bit of duplicate code, which is not ideal, and also there is still the problem that just clicking "start" multiple times would create multiple intervals running in parallel, overwriting the previous interval ID with the new one, making the old intervals again unclearable.
My suggestion for solving both of these issues is to create functions startInterval and stopInterval, where startInterval would also first call stopInterval before setting up a new interval, and stopInterval clearing the old one if it exists. Then you can call startInterval both on page load and on "start" button click, and stopInterval on "stop" button click:
let myInterval = null
function startInterval () {
stopInterval()
myInterval = setInterval(setTime, 10)
}
function stopInterval () {
if (!myInterval) return
clearInterval(myInterval)
myInterval = null
}
startInterval()
startButton.addEventListener('click', startInterval)
stopButton.addEventListener('click', stopInterval)
From the wording of the question I'm not sure whether you really want to auto-start the timer though. In case you don't, you can simply remove the startInterval() line from my example (or in case of the original code, change the first assignment to let myInterval = null like in the second example).
This line:
const myInterval = setInterval(setTime, 10);
actually starts the interval.
You need to change it to this:
let myInterval;
startButton.addEventListener('click', () => {
myInterval = setInterval(setTime, 10);
})
This ensures that everytime you press the start button the interval will be assigned to the myInterval variable again which can then be cleared by the stop button

Need Help Coding a Loop Using "For" Statement in JavaScript

I need to make a countdown happen from 10 to 0 using a loop. The loop should replace the need to repeat the code 10X. I also neeed to display the countdown to the user in the HTML. Help!
<script>
function StartTheCountdown()
{
var Countdown = 10;
// Used to keep track of actual time.
// 1000 = 1 second because we are using milliseconds.
var timeout = 10000;
setTimeout(() => {
document.getElementById("CountDownDisplay").innerHTML = "Blastoff!";
Countdown = Countdown - 1;
}, timeout)
timeout = timeout - 1000;
// We need to do this 10 times **************************************
setTimeout(() => {
document.getElementById("CountDownDisplay").innerHTML = Countdown;
Countdown = Countdown - 1;
}, timeout)
timeout = timeout - 1000;
}
</script>
Use setTimeout to repeatedly call the function until count is zero, and then display a message.
// Cache your element
const div = document.querySelector('div');
// Initialise your count to 10
function countdown(count = 10) {
// Update your element textContent
div.textContent = `T-minus: ${count}`;
// Call the function again if the count
// is greater than 0 with a decremented count
if (count > 0) {
setTimeout(countdown, 1000, --count);
// Otherwise update the textContent of the
// element with a message
} else {
div.textContent = 'Blast off!';
}
}
countdown();
<div></div>
Additional documentation
Template/string literals

Why can't I change this global variable from function? (JavaScript)

Simple countdown project.
Desired outcome:
global variable determines default time.
then slider value overrides that variable.
PROBLEM: slider value changes locally but global value stays the same.
I've watched tutorials all day on variable scopes and still don't see what's wrong because:
Global is declared with "var" outside function: var initialMinutes = 5
attempt to update value inside function looks like this:
const change = slider.addEventListener('change', setMins)
function setMins() {
initialMinutes = slider.value
}
I have also tried with window.variable here and there to no avail.
I hope someone can help. This is becoming difficult to scope with.
const countdown = document.getElementById('countdown')
const slider = document.getElementById('slider')
const startBtn = document.getElementById('btn-startStop')
const resetBtn = document.getElementById('btn-reset')
// Event Listeners
const change = slider.addEventListener('change', setMins)
const start = startBtn.addEventListener('click', startStop)
const reset = resetBtn.addEventListener('click', resetApp)
// Time
var initialMinutes = 15
countdown.innerHTML = initialMinutes+':00'
const initialDuration = initialMinutes * 60
let time = initialMinutes * 60
let interval = null
let status = 'stopped'
function updateCountdown() {
const minutes = Math.floor(time / 60)
let seconds = time % 60
seconds = seconds < 10 ? '0'+ seconds : seconds
if( time < 1) {
clearInterval(interval)
startBtn.innerHTML = 'START'
status = 'stopped'
countdown.innerHTML = `00:00`
} else {
countdown.innerHTML = `${minutes}:${seconds}`
time--;
}
}
function startStop() {
if(status === 'stopped') {
interval = setInterval(updateCountdown, 50)
startBtn.innerHTML = 'STOP'
status = 'running'
} else {
clearInterval(interval)
startBtn.innerHTML = 'START'
status = 'stopped'
}
}
function setMins() {
initialMinutes = slider.value
countdown.innerHTML = slider.value+':00'
}
function resetApp() {
clearInterval(interval);
document.getElementById('countdown').innerHTML = '00:00'
startBtn.innerHTML = 'START'
status = 'stopped'
}
Codepen link included for clarity:
https://codepen.io/donseverino/pen/YzWBJYV
Got it! It was not a problem of scope but of variable assignment.
updateCountdown() uses time and time = initialMinutes * 60
I thought changing initialMinutes would automatically change its value inside time but it doesn't.
Reassigning time inside the function solves it.
function setMins() {
initialMinutes = slider.value
time = initialMinutes * 60
}

setInterval goes to negative

I'm buidling this pomodoro app.
https://jsfiddle.net/yvrs1e35/
I got few problems with the timer.
startBtn.addEventListener('click', function(){
minutes.innerHTML = sessionTime.innerHTML - 1
seconds.innerHTML = 59
var timer = setInterval(()=>{
if(Number(minutes.innerHTML) != 0 && Number(seconds.innerHTML) != 0){
seconds.innerHTML--
if(Number(seconds.innerHTML) == 0){
seconds.innerHTML = 59;
minutes.innerHTML--
}
}else if (Number(minutes.innerHTML) == 0 && Number(seconds.innerHTML) == 0){
clearInterval(timer)
}
},1000)
resetBtn.addEventListener('click', function(e){
e.preventDefault();
breakTime.innerHTML = 5
sessionTime.innerHTML = 25
minutes.innerHTML = "00"
seconds.innerHTML = "00"
clearInterval(timer)
})
pauseBtn.addEventListener('click', function(e){
e.preventDefault();
})
})
It works if the timer if there is more than 1 minute left on the interval.
If it goes under 1 minute, even though i have this if in the interval
else if (Number(minutes.innerHTML) == 0 && Number(seconds.innerHTML) == 0){
clearInterval(timer)
}
seconds and minutes go on negative ( after 0:0, timer shows -1:59)
I though that else if statement would stop the interval when both minutes and seconds reach 0, but it doesnt for some reason.
#also if i press startbtn multiple times, the timer starts multiple times, and the seconds go 2x 3x 4x faster, how can I stop the startbtn until the timer reaches 0:0?
Can i get any help?
.innerHTML is an expensive operation. Storing data inside the DOM like this is an antipattern; extracting it and manipulating it stringifies and de-stringifies numbers for no reason. Store state in your JS script and update the DOM content only when a rendering change is necessary. In other words, consider it write-only.
The interval runs multiple times; you'll need a flag to prevent re-triggers (or clearInterval before resetting it). Setting interval to undefined is a good way to indicate that the clock isn't running.
Lastly, setInterval with a cooldown of 1000 is a poor choice for timekeeping. It will drift quite a bit depending on scheduling interruptions and other random factors; the 1000 means "wait at least 1000 milliseconds before firing the callback". Instead, use Date for accuracy.
I'd work entirely in milliseconds and convert to minutes and seconds only for the formatted output. This follows the principle described in the first paragraph about separating presentation from logic.
Here's a proof of concept to illustrate the above points. Of course, if you're doing the pomodoro for fun, sticking to setInterval(() => ..., 1000) does make the code simpler, but I think it's instructive to see it from a couple angles if nothing else.
const padTime = t => (Math.floor(t) + "").padStart(2, 0);
const timeFmt = t => `${padTime(t / 60000)}:${
padTime(t / 1000 % 60)}`;
const run = () => {
interval = setInterval(() => {
if (interval) {
display.textContent = timeFmt(end - Date.now());
}
if (Date.now() >= end) {
clearInterval(interval);
interval = undefined;
}
}, 100);
};
let interval;
let pause;
const initialMinutes = 2;
const duration = initialMinutes * 60000;
const time = Date.now();
let end = time + duration;
const display = document.querySelector("h1");
display.textContent = timeFmt(end - time);
const [startBtn, pauseBtn, resetBtn] =
document.querySelectorAll("button");
startBtn.addEventListener("click", e => {
clearInterval(interval);
if (!interval) {
if (pause) {
end += Date.now() - pause;
pause = undefined;
}
else {
end = Date.now() + duration;
}
}
run();
});
resetBtn.addEventListener("click", e => {
clearInterval(interval);
interval = undefined;
const time = Date.now();
end = time + duration;
display.textContent = timeFmt(end - time);
});
pauseBtn.addEventListener("click", e => {
if (interval) {
pause = Date.now();
clearInterval(interval);
interval = undefined;
}
});
<h1></h1>
<div>
<button>start</button>
<button>pause</button>
<button>reset</button>
</div>
if(Number(minutes.innerHTML) != 0 && Number(seconds.innerHTML) != 0)
Changing the "&&" to "||" should fix one problem (which is that it is stuck at 0:59). If you add another condition to
if(Number(seconds.innerHTML) == 0){
where the if condition is only true, if seconds == 0 AND minutes > 0, then all problems should be solved.
Your negative time comes from starting at 0 and because of this:
minutes.innerHTML = sessionTime.innerHTML - 1
seconds.innerHTML = 59
It changes minutes to negative value and sets seconds to 59. You should add some validation before this and don't start clock.
setInterval doesn't guarantee that your function will execute in the precise interval, just that it wouldn't execute earlier. This way, on a slow/loaded computer, the function could be called after the interval is already elapsed.
In other words, you probably wish to check if the timer has already elapsed, not if it's just about to do so.
(Number(minutes.innerHTML) <= 0 && Number(seconds.innerHTML) <= 0)

How am I able to add timing to javascript that alters text every second?

I would like this code to count up from 0 to 940 (very fast) and alter the text every time it updates
Here's my code (inside my head tag):
<script type="text/javascript">
function sleep(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
function onLoad(){
var x = document.getElementById("numberID");
var n = 940;
var text = "";
for(i = 0;i < n + 1;i++){
text = i;
x.innerHTML = text;
sleep(1);
}
}
</script>
At the moment, it just waits a second then displays '940' on screen and doesn't display it counting up.
Any help would be appreciated, thanks!
Here's the code I recently put in, still doesn't work:
const x = document.getElementById("numberID");
function newFrame(duration, start = performance.now()) {
requestAnimationFrame((now) => {
const elapsed = now - start;
x.innerText = Math.max(0, Math.min(duration,
Math.round(elapsed)));
if(elapsed < duration)
newFrame(duration, start);
})
}
}
newFrame(940);
Using a while loop to "sleep" is going to block the page's thread and nothing else can happen in the meantime. This is considered bad practice.
setTimeout guarantees that at least the defined time has passed, but can take (much) longer. This is imprecise, and especially bad for shorter intervals. Same with setInterval. They're also not recommended for callbacks that involve updating the DOM.
What you need to do is use a requestAnimationFrame.
function newFrame(duration, start = performance.now()) {
requestAnimationFrame((now) => {
const elapsed = now - start
console.log(`time passed: ${elapsed} ms`)
if(elapsed < duration)
newFrame(duration, start)
})
}
newFrame(940)
In your specific case, I'd replace the console.log statement put there for didactic purposes, with something along the lines of:
x.innerText = Math.max(0, Math.min(duration, Math.round(elapsed)))
Here's what that would look like:
const x = document.getElementById("numberID")
function newFrame(duration, start = performance.now()) {
requestAnimationFrame((now) => {
const elapsed = now - start
x.innerText = Math.max(0, Math.min(duration, Math.round(elapsed)))
if(elapsed < duration)
newFrame(duration, start)
})
}
newFrame(940)
<span id="numberID"></span>
The sleep function is not doing anything, what you need is a setTimeout to display the text at every x milliseconds.
Something like the below will work.
let x = null;
let timeout = null;
const changeText = (text) => {
x.innerHTML = text;
clearTimeout(timeout);
}
function onLoad() {
x = document.getElementById("numberID");
const n = 940;
const t = .01; // in seconds
for( let i = 0; i <= n; i++) {
timeout = setTimeout( () => changeText((i+1).toString()), (t * i) * 1000);
}
}
onLoad();
<span id="numberID"></span>

Categories

Resources