Set up a pause/resume-able timer in javascript - javascript

Is there a way to create a "timer"(or stopwatch) class, that the timers created using this class can be paused and resumed on triggering an event, e.g. clicking a button?
I tried to create the class and create timer objects using it, but the timer cannot be paused once it starts.
My attempt on creating this class:
class countdown {
constructor(min, sec) {
this.mins = min;
this.secs = sec;
this.handler = 0;
}
static setTimer(x,minfield,secfield) {
this.handler = setTimeout(() => {
if (x.mins == 0 && x.secs == 0) {
clearTimeout();
} else {
if (x.secs == 0) {
x.mins -= 1;
x.secs = 59;
} else {
x.secs -= 1;
}
}
this.updateTimer(x,minfield,secfield);
this.setTimer(x,minfield,secfield)
}, 1000)
}
static updateTimer(x,minfield, secfield){
document.getElementById(minfield).innerHTML = x.mins;
document.getElementById(secfield).innerHTML = x.secs;
}
static stopTimer(x,minfield,secfield) {
// document.getElementById(minfield).innerHTML = x.mins;
// document.getElementById(secfield).innerHTML = x.secs;
clearTimeout(x.handler);
}
}
Usage:
let countdown1 = new countdown(15,0);
let countdown_act = false;
document.getElementById('button').addEventListener( 'click', () => {
if (!countdown_act) {
countdown.setTimer(countdown1, 'ctdwn-mins', 'ctdwn-secs');
countdown_act = true;
} else {
countdown.stopTimer(countdown1, 'ctdwn-mins', 'ctdwn-secs');
countdown_act = false;
}
console.log(countdown_act);
}
)
The countdown_act flag is used for indicating the state of the timer.

There's no need for any of your methods to be static. Also, it's much simpler and easier to use if you just record seconds and then calculate minutes when you need the output. As a standard, capitalize the name of classes. Finally, you're probably looking for setInterval, not timeout.
Think of a real-world timer. You can set it, start it, pause it, and stop it. Stopping a timer is really just restarting it and pausing it, and it's set upon creation, so let's make a Timer with start, pause, set and stop or reset. This still behaves the same way as yours in the background by using a 1s timeout, but it's a lot cleaner and easier for someone reading your code to understand:
class Timer {
constructor(seconds, callback) {
this.originalTime = seconds;
this.remainingTime = seconds;
this.timeout = null;
this.callback = callback; // This is what the timer does when it ends
}
start() {
this.timeout = setInterval(() => {
this.remainingTime -= 1; // Remove a second from the timer
if(this.remainingTime === 0) {
this.callback(); // Run the callback function
this.stop(); // Stop the timer to prevent it from counting into the negatives
}
}, 1000);
}
pause() {
clearInterval(this.timeout);
}
stop() {
this.pause();
this.remainingTime = this.originalTime; // Reset the time
}
setTime(seconds) {
this.originalTime = seconds; // Set a new time
this.stop(); // Have to restart the timer to account for the new time
}
get remaining() {
return {
seconds: this.remainingTime % 60,
minutes: Math.floor(this.remainingTime / 60)
}
}
}

Related

How to pause setTimeout and then continue it?

https://jsfiddle.net/mhLaj3qy/1/
const changeTimeset = setTimeout(()=>{
if (is_paused) return;
bgImage.style.backgroundImage = array[current]
changeBackgroundImages(data, ++current % array.length)
}, a)
function chandgeBackgroundImageOnMouse() {
samoyed.addEventListener("mouseover", ()=>{
bgImage.style.backgroundImage = house
is_paused = true
})
samoyed.addEventListener("mouseleave", ()=>{
is_paused = false
})
}
chandgeBackgroundImageOnMouse()
}
How to pause setTimeout and then continue it? It should be like when it hovered on text - picture stops auto slider, when it mouseout - auto slider is working
Here i tried to do it with it_paused = false/true. But nothing succeeded. What am i doing wrong?
In this case, you may try using setInterval instead of setTimeout.
About setInterval:
https://www.w3schools.com/jsref/met_win_setinterval.asp
In fact, setTimeout internally can't be paused, but we can work around, it by when pause clearing the old setTimeout and checking the difference in time, and setting it in a variable called remaining, then when we resume creating a new setTimeout with the remaining time.
class Timer {
constructor(callback, delay) {
this.remaining = delay
this.callback = callback
this.timerId = undefined;
this.start = undefined;
this.resume()
}
pause() {
window.clearTimeout(this.timerId);
this.timerId = null;
this.remaining -= Date.now() - this.start;
};
resume() {
if (this.timerId) {
return;
}
this.start = Date.now();
this.timerId = window.setTimeout(this.callback, this.remaining);
};
}
const pause = document.getElementById('pause');
const resume = document.getElementById('resume');
var timer = new Timer(function() {
alert("Done!");
}, 5000);
pause.addEventListener('click', () => {
timer.pause();
})
resume.addEventListener('click', () => {
timer.resume();
})
<button id="pause">Pause</button>
<button id="resume">Resume</button>

Clear interval inside object method

This code is part of a game where the user has a certain amount of time to input an answer.
I want to be able to clear the interval from outside of the object if the user decides to submit an answer before the allotted time has elapsed.
I have tried returning the interval with an ID so that I can call it later and whilst this does allow me to clear it from outside the outside the object, it means that the code inside the interval function is never run.
const clock = {
timer: 30,
countdown() {
let interval = setInterval(function () {
selectors.timerDisplay.textContent = clock.timer
clock.timer--
if (clock.timer < 0) {
clearInterval(interval)
selectors.wordSubmit.click();
}
}, 1000)
},
}
I appreciate I may have simply set myself up badly to clear this interval, therefore any suggestions on how I could improve it would be appreciated.
Thanks in advance.
You can use Arrow functions to leverage the context of this from your object clock
Add a method i.e clear.
Use this context to reference your inner attributes.
const clock = {
timer: 3,
interval: 0,
reset() {
this.timer = 3;
this.interval = 0;
},
clear() {
clearInterval(this.interval);
this.reset();
},
countdown() {
this.interval = setInterval(() => {
//selectors.timerDisplay.textContent = clock.timer
this.timer--;
console.log(this.timer)
if (this.timer < 0) {
clearInterval(this.interval)
//selectors.wordSubmit.click();
}
}, 1000);
},
}
clock.countdown();
setTimeout(function() {
clock.clear();
}, 1500)
See? the interval function ends after 1.5secs
You can expose a method to clear the interval
const selectors = document.querySelector('div');
const clearTimer = document.querySelector('button');
const clock = {
timer: 5,
// Initiate the interval
int: null,
// This exposes a way to clear the interval outside of the object
clearTimer() {
clearInterval(this.int);
},
countdown() {
// This is where you define this.int
this.int = setInterval(() => {
//selectors.timerDisplay.textContent = clock.timer
selectors.innerText = clock.timer.toString();
clock.timer--
console.log(clock.timer);
if (clock.timer < 0) {
this.clearTimer();
//selectors.wordSubmit.click();
}
}, 1000)
},
}
clock.countdown();
clearTimer.addEventListener('click', () => {
clock.clearTimer();
})
<div>clock.timer</div>
<button>clear timer</button>
As has been suggested in the comments, simply return the interval so it can be stopped later. For example
countdown() {
let interval = setInterval(function () {
selectors.timerDisplay.textContent = clock.timer
clock.timer--
if (clock.timer < 0) {
clearInterval(interval)
selectors.wordSubmit.click();
}
}, 1000)
return interval // added this line
},
Then, consumers can cancel the interval when they want
const interval = clock.countdown()
// and later...
clearInterval(interval)

Calling functions one from the other and vice versa

I have two timers with two boolean variables and two functions. The first timer is triggered by a click that put true one of the booleans; once the timer reach some conditions this timer is off setting false the boolean and another timer is triggered setting true the second boolean and calling the second timer.
Same once the second timer reach some conditions, the timer is off setting the second boolean false and I try to trigger the first timer setting the first boolean true and calling the first timer, but it dosn't work.
I cannot call the function that is declared underneath the funcion where I do the call.
My code in JavaScript is:
var active=false;
var activeBreak=false;
...
function breakDown(){
if(activeBreak){
...
if(some conditions){
active=true;
activeBreak=false;
countDown();// **This call doesn't work.Black in jsbin**
}
}
}
function countDown(){
if(active){
...
if(someConditions){
active=false;
activeBreak=true;
breakDown();// *This call works. Blue in jsbin*
}
}
}
...
$("#circle").click(function(){
active=true;
countDown(); // *This call works*/
});
Try
$(document).ready(function(){
/* Global variables */
var state = 'STOPPED';
var timeSession = 1;
var timeBreak = 1;
var timeout = 0;
function resetTimer(time) {
timeout = (new Date()).getTime() + (time*60*1000);
}
function changeState() {
if (state === 'SESSION') {
state = 'BREAK';
resetTimer(timeBreak);
} else if (state === 'BREAK' || state === 'STOPPED') {
state = 'SESSION';
resetTimer(timeSession);
}
$('#text').text(state);
}
/* Function for the countdown ****************************/
function countDown (){
if (state !== 'STOPPED') {
var time = (new Date()).getTime();
if (timeout <= time) {
changeState();
return;
}
var s = Math.floor((timeout - time) / 1000);
var m = Math.floor(s / 60);
s = s - m*60;
$('#timer').text(""+((timeout - time) / 1000)+" "+timeout+" "+m+":"+("0"+s).substr(-2));
}
setTimeout(countDown, 1000);
}
/*****Click Break ********************/
$("#breakMinus").click(function(){
if (timeBreak > 1) {
--timeBreak;
}
$("#breakContent").html(timeBreak);
});
$("#breakPlus").click(function(){
++timeBreak;
$("#breakContent").html(timeBreak);
});
/******Click Session Length****************/
$("#seMinus").click(function(){
if (timeSession > 1) {
--timeSession;
}
$("#seContent").html(timeSession);
});
$("#sePlus").click(function(){
++timeSession;
$("#seContent").html(timeSession);
});
/***********Click circle****************/
$("#circle").click(changeState);
setTimeout(countDown, 1000);
$('#text').text(state);
});

Stopping timer not working:

I have THIS timer in my project.
When it runs out, it shows a Time Up screen, which works fine.
But when the player is Game Over, i show the Game Over screen, but the timer keeps running and when it hits 00:00 then it switches to the Time Up screen.
How can i make this timer stop counting down and set to 00:00 again?
I tried adding a function like this:
CountDownTimer.prototype.stop = function() {
diff = 0;
this.running = false;
};
I also tried to change the innerHTML but its obvious that its just changing the numbers without stopping the timer and after a second it will show the count down again... I don't know what to call.
//Crazy Timer function start
function CountDownTimer(duration, granularity) {
this.duration = duration;
this.granularity = granularity || 1000;
this.tickFtns = [];
this.running = false;
}
CountDownTimer.prototype.start = function() {
if (this.running) {
return;
}
this.running = true;
var start = Date.now(),
that = this,
diff, obj;
(function timer() {
diff = that.duration - (((Date.now() - start) / 1000) | 0);
if (diff > 0) {
setTimeout(timer, that.granularity);
} else {
diff = 0;
that.running = false;
}
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
}());
};
CountDownTimer.prototype.onTick = function(ftn) {
if (typeof ftn === 'function') {
this.tickFtns.push(ftn);
}
return this;
};
CountDownTimer.prototype.expired = function() {
return !this.running;
};
CountDownTimer.parse = function(seconds) {
return {
'minutes': (seconds / 60) | 0,
'seconds': (seconds % 60) | 0
};
};
window.onload = function () {
var display = document.querySelector('#countDown'),
timer = new CountDownTimer(timerValue),
timeObj = CountDownTimer.parse(timerValue);
format(timeObj.minutes, timeObj.seconds);
timer.onTick(format).onTick(checkTime);
document.querySelector('#startBtn').addEventListener('click', function () {
timer.start();
});
function format(minutes, seconds) {
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
display.textContent = minutes + ':' + seconds;
}
function checkTime(){
if(this.expired()) {
timeUp();
document.querySelector('#startBtn').addEventListener('click', function () {
timer.start();
});
}
}
};
Instead of recursively calling setTimeout, try setInterval instead. You could then store a reference to the timer:
this.timer = setInterval(functionToRunAtInterval, this.granularity);
and kill it when the game finishes::
clearInterval(this.timer)
(see MDN's docs for more info on setInterval)
It's been a while and I'm not sure if you've figured it out but check out the fiddle below:
https://jsfiddle.net/f8rh3u85/1/
// until running is set to false the timer will keep running
if (that.running) {
if (diff > 0) {
setTimeout(timer, that.granularity);
} else {
diff = 0;
that.running = false;
}
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
}
I've added a button that causes running to be set to false which stops the timer.
Button:
<button id="stop">Game Over</button>
Code:
$( "#stop" ).click(function() {
timer.running = false;
});
So that should hopefully get you to where you need to be.
Similar to Tom Jenkins' answer, you need to cancel the next tick by avoiding the diff > 0 branch of your if statement. You could keep your code as it stands and use your suggested stop method, however, you'd need to change your logic around handling ticks to check that both running === true and a new param gameOver === false.
Ultimately, I think your problem is that no matter whether the timer is running or not, you'll always execute this code on a tick:
obj = CountDownTimer.parse(diff);
that.tickFtns.forEach(function(ftn) {
ftn.call(this, obj.minutes, obj.seconds);
}, that);
If you have a game over state, you probably don't want to call the provided callbacks, so add some conditional check in there.

Stop countdown on click

I want my countdown to stop on the click of a submit button, i searched in some pages,
but I didn't found anything.
Here is the code i want to stop on click
function countDown (count) {
if (count > 0) {
var d = document.getElementById("countDiv");
d.innerHTML = count;
setTimeout (function() { countDown(count-1); }, 1000);
document.getElementById('tiempo').value = count;
}
else
document.location = "timeover.php";
}
document.getElementById("palabra").focus();
countDown(5);
</script>
You have to save reference to timeout (actually return value of timeout will be number) and use it to cancel timeout.
var timeout = window.setTimeout(function () {
// do something
}, 1000);
// clear timeout
window.clearTimeout(timeout);
You probably got the idea. By the way, you should probably look at setInterval method since it would be better in this situation. Interval will "tick" as long until you cancel it.
Try something like this:
var stopped = false;
function countDown (count) {
if (count > 0) {
var d = document.getElementById("countDiv");
d.innerHTML = count;
document.getElementById('tiempo').value = count;
if (!stopped) {
setTimeout (function() { countDown(count-1); }, 1000);
}
}
else
document.location = "timeover.php";
}
document.getElementById("palabra").focus();
document.getElementById("mySubmitId").onclick = function () {
stopped = true;
};
countDown(5);

Categories

Resources