VueJS how to watch a value and animate when it changes - javascript

In my vuejs-application I want to animate a number/value when it changes. The value is based on a response from the vuex-store.
so I've got the animation running, but for some reason the animation loop is infinite and just continues all the time.
data() {
return {
interval: false,
hitsCount: 0
}
},
watch: {
hitsCount: function() {
clearInterval(this.interval);
this.interval = window.setInterval( function() {
let change = this.hitsCount / 10;
change = change >= 0 ? Math.ceil(change) : Math.floor(change);
this.hitsCount = this.hitsCount + change;
}.bind(this),20);
}
}
the template is simply:
<div> {{hitsCount}} </div>
The value hitsCount comes from a request:
this.$store.dispatch("store/data", {}).then(response => {
this.hitsCount = response.total;
.../
});
the value changes each time there is a new request.
As mentioned before, the animation starts right away and keeps counting endlessly - what am I missing here?

Anyway, it's not good idea to watch property you are changing.
Divide it into two variables.
data() {
return {
hitsCount: 0,
hitsCountDisplay: 0
}
},
watch: {
hitsCount() {
clearInterval(this.interval);
if (this.hitsCount !== this.hitsCountDisplay) {
let value = this.hitsCountDisplay;
const change = (this.hitsCount - this.hitsCountDisplay) / 30;
this.interval = setInterval(() => {
value += change;
this.hitsCountDisplay = Math.round(value);
if (this.hitsCountDisplay === this.hitsCount) {
clearInterval(this.interval);
}
}, 20);
}
}
}
and
<div> {{hitsCountDisplay}} </div>
Here is a working example: https://jsfiddle.net/knvyrx8j/

You shouldn't update a property inside its watcher, this will cause an infinite loop, but you could run the animation inside the callback of your action dispatcher :
this.$store.dispatch("store/data", {}).then(response => {
let count = response.total;
clearInterval(this.interval);
this.interval = window.setInterval( function() {
let change = count / 10;
change = change >= 0 ? Math.ceil(change) : Math.floor(change);
this.hitsCount = this.hitsCount + change;
}.bind(this),20);
});

Related

React clearInterval not working in class Component

I am trying to make a timer that will start at the beginning of the sorting visualization and run until sorting is over. The timer starts accordingly but does not stop after sorting. this works absolutely fine in functional Component (with useEffect hook) but does not work in class Component.
here is my startTimer function -
startTimer = () => {
let isAlgo = this.state.isAlgorithmSortOver;
let interval;
if (isAlgo === true) {
interval = setInterval(() => {
this.setState({
time: this.state.time + 10,
});
}, 10);
} else if (isAlgo === false) {
clearInterval(interval);
}
// return () => clearInterval(interval);
};
and here is startVisualizer function -
startVisualizer = () => {
let steps = this.state.arraySteps;
let colorSteps = this.state.colorSteps;
this.clearTimeouts();
let timeouts = [];
let i = 0;
while (i < steps.length - this.state.currentStep) {
let timeout = setTimeout(() => {
let currentStep = this.state.currentStep;
this.setState({
array: steps[currentStep],
colorElement: colorSteps[currentStep],
currentStep: currentStep + 1,
isAlgorithmSortOver: false,
});
//? comparing the currentStep with arraySteps and the state of isAlgorithmSortOver will remain false until the array is fully sorted.. Adding '+ 1' to currentStep because the arraySteps state always will be '+1' bigger than the currentStep..
if (currentStep + 1 === i) {
this.setState({
isAlgorithmSortOver: true,
});
}
timeouts.push(timeout);
}, this.state.delayAnimation * i);
i++;
}
this.startTimer();
this.setState({
timeouts: timeouts,
// isAlgorithmSortOver: false,
});
};
because you are not clearing the interval. Try to save interval's id to state like this:
startTimer = () => {
let isAlgo = this.state.isAlgorithmSortOver;
if (isAlgo === true) {
interval = setInterval(() => {
this.setState({
time: this.state.time + 10,
});
}, 10);
this.setState({interval})
}
};
Then you can then call clearInterval wherever you want, like this:
clearInterval(this.state.interval)
You also should bring out timeouts.push(timeout) from setTimeout where in while. Because, otherwise timeouts.push(timeout) does not work synchronously, it works after a while.

Set up a pause/resume-able timer in 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)
}
}
}

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)

Selective timeout based events handling: immediate first, debounce next

Let's say there are random sequences of external actions (e.g. scroll events). I need to handle the first action immediately, then dismiss all actions occurred with intervals less than some given delta, and then handle the next one which should be delayed for that delta. Further actions should be processed in the same manner.
This looks like a combination of debounce-immediate and simple debounce. I prepared a diagram to demonstrate the idea.
What is the best solution/approach here? I wonder if there is some ready-made pattern...
UPDATE
I would like to thank all participants! For the research I created plunker with four five different realizations suggested in answers: https://plnkr.co/N9nAwQ.
const handler = [
processEvent, // normal
debounceNext(processEvent, DELAY), // dhilt
makeRateLimitedEventHandler(DELAY, processEvent), // user650881
debounceWithDelay(processEvent, DELAY, 0), // willem-dhaeseleer
_.debounce(processEvent, DELAY, {leading: true}) // lodash debounce + leading,
debounceish(DELAY, processEvent) //Mikk3lRo
];
A great news was the Lodash has a leading-flag debounce implementation which satisfies the issue (thanks to Willem D'Haeseleer). And here is the cool demo from Mikk3lRo' answer, he also provided some useful synthesis.
I investigated the sources and the results: form just visual point to memory allocation stuff... I didn't find any performance issues, and the views seem okey. So the ultima ratio was the code itself. All sources were converted to ES6 (as you can see in Plunker) for I can compare them fully. I excluded my own try (it is a bit excessive, despite I like how it looks). The timestamp version is very interesting! The postDelay version's nice, though it wasn't a requested feature (so that snippet demo has double delay for two lodash demos).
I decided not to have a lodash dependency (in other way I certainly would use lodash debounce with leading option), so I chose debounceish by Mikk3lRo.
PS I would like to share that little bounty (unfortunately there is no such an option) or even take some more scores from my reputation for it (but not 200, is too much and would be unfair to the winner which would have only 100). I even can't vote twice... Nevermind.
A very simple solution in vanilla JS using a single timer:
function debounceish(delta, fn) {
var timer = null;
return function(e) {
if (timer === null) {
//Do now
fn(e);
//Set timer that does nothing (but is not null until it's done!)
timer = setTimeout(function(){
timer = null;
}, delta);
} else {
//Clear existing timer
clearTimeout(timer);
//Set a new one that actually does something
timer = setTimeout(function(){
fn(e);
//Set timer that does nothing again
timer = setTimeout(function(){
timer = null;
}, delta);
}, delta);
}
};
}
function markEvt(e) {
var elm = document.createElement('div');
elm.style.cssText = 'position:absolute;background:tomato;border-radius:3px;width:6px;height:6px;margin:-3px;';
elm.style.top = e.clientY + 'px';
elm.style.left = e.clientX + 'px';
document.body.appendChild(elm);
}
document.addEventListener('click', debounceish(2000, markEvt));
<p>Click somewhere (2000ms delta) !</p>
Comparing 6 proposals using the same type of visualization:
var methods = {
default: function(delay, fn) {
return fn;
},
dhilt_debounceNext: (delay, cb) => {
let timer = null;
let next = null;
const runTimer = (delay, event) => {
timer = setTimeout(() => {
timer = null;
if(next) {
next(event);
next = null;
runTimer(delay);
}
}, delay);
};
return (event) => {
if(!timer) {
cb(event);
}
else {
next = cb;
clearTimeout(timer);
}
runTimer(delay, event);
}
},
Mikk3lRo_debounceish(delta, fn) {
var timer = null;
return function(e) {
if (timer === null) {
//Do now
fn(e);
//Set timer that does nothing (but is not null until it's done!)
timer = setTimeout(function(){
timer = null;
}, delta);
} else {
//Clear existing timer
clearTimeout(timer);
//Set a new one that actually does something
timer = setTimeout(function(){
fn(e);
//Set timer that does nothing again
timer = setTimeout(function(){
timer = null;
}, delta);
}, delta);
}
};
},
user650881_makeRateLimitedEventHandler: function(delta_ms, processEvent) {
var timeoutId = 0; // valid timeoutId's are positive.
var lastEventTimestamp = 0;
var handler = function (evt) {
// Any untriggered handler will be discarded.
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = 0;
}
var curTime = Date.now();
if (curTime < lastEventTimestamp + delta_ms) {
// within delta of last event, postpone handling
timeoutId = setTimeout(function () {
processEvent(evt);
}, delta_ms);
} else {
// long enough since last event, handle now
processEvent(evt);
}
// Set lastEventTimestamp to time of last event after delta test.
lastEventTimestamp = Date.now();
};
return handler;
},
Willem_DHaeseleer_debounceWithDelay: (delay, func) => {
let postDebounceWait;
let timeOutLeading = false;
const debounced = _.debounce((...args) => {
// wrap the handler so we can add an additional timeout to the debounce invocation
if (timeOutLeading) {
/*
for the first invocation we do not want an additional timeout.
We can know this is the leading invocation because,
we set timeOutLeading immediately to false after invoking the debounced function.
This only works because the debounced leading functionality is synchronous it self.
( aka it does not use a trampoline )
*/
func(...args);
} else {
postDebounceWait = setTimeout(() => {
func(...args)
}, delay);
}
}, delay, {leading: true});
return (...args) => {
// wrap the debounced method it self so we can cancel the post delay timer that was invoked by debounced on each invocation.
timeOutLeading = true;
clearTimeout(postDebounceWait);
debounced(...args);
timeOutLeading = false;
}
},
Willem_DHaeseleer_lodashWithLeading: (delta, cb) => {
return _.debounce(cb, delta * 2, {leading: true});
},
Javier_Rey_selfCancelerEventListener: function (delta, fn) {
return function(ev) {
var time = new Date().getTime();
if (ev.target.time && time - ev.target.time < delta) {return;}
ev.target.time = time;
fn(ev);
};
},
};
var method_count = 0;
var colors = ['grey', 'tomato', 'green', 'blue', 'red', 'orange', 'yellow', 'black'];
function markEvt(method) {
var style = 'position:absolute;border-radius:3px;width:6px;height:6px;margin:-3px;';
style += 'background:' + colors[method_count] + ';';
if (method_count > 0) {
style += 'transform:rotate(' + Math.floor(360 * method_count / (Object.keys(methods).length - 1)) + 'deg) translateY(-8px);';
}
var elm = document.createElement('div');
elm.innerHTML = '<span style="width:.8em;height:.8em;border-radius:.4em;display:inline-block;background:' + colors[method_count] + '"></span> ' + method;
document.body.appendChild(elm);
method_count++;
return function(e) {
elm = document.createElement('div');
elm.style.cssText = style;
elm.style.top = e.clientY + 'px';
elm.style.left = e.clientX + 'px';
document.body.appendChild(elm);
};
}
for (var method in methods) {
document.addEventListener('click', methods[method](2000, markEvt(method)));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
Note that I needed to make minor adjustments to some of the methods to get a common interface. Adapting Cully's answer took more effort than I was willing to put in considering the comments suggest it doesn't do what the OP wants anyway.
It should be pretty clear that Javier Rey's approach behaves completely differently from the rest. Dhilt, user650881 and my own methods seem consistent. Both of Willem D'Haeseleer's methods have double the delay (and other subtle differences), but seem to behave consistently too. As far as I understand the double delay is completely intentional, though that is not how I understand the OP.
I would say that Willem D'Haeseleer's lodash method is without a doubt the simplest - if you already use lodash that is. Without external dependencies my method is IMO simplest - but I may be biased on that one ;)
You might track the last event time and only create a timer event when a follow-up check is required.
function makeRateLimitedEventHandler(delta_ms, processEvent) {
var timeoutId = 0; // valid timeoutId's are positive.
var lastEventTimestamp = 0;
var handler = function (evt) {
// Any untriggered handler will be discarded.
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = 0;
}
var curTime = Date.now();
if (curTime < lastEventTimestamp + delta_ms) {
// within delta of last event, postpone handling
timeoutId = setTimeout(function () {
processEvent(evt);
}, delta_ms);
} else {
// long enough since last event, handle now
processEvent(evt);
}
// Set lastEventTimestamp to time of last event after delta test.
lastEventTimestamp = Date.now();
};
return handler;
}
var DELTA_MS = 5000;
var processEvent = function (evt) { console.log('handling event'); };
el.addEventHandler('some-event', makeRateLimitedEventHandler(DELTA_MS, processEvent));
The behavior in your visual is no different then the standard lodash debouncing behavior with the leading option, the only difference is that your only displaying half of the delta instead of the full delta.
Therefore, your solution can be as simple as this.
_.debounce(cb, delta * 2, {leading: true});
https://lodash.com/docs/4.17.4#debounce
If you want the last delay to be longer, you can solve that by wrapping both the debounced method and the handler. That way you can set the timeout in the handler, and cancel it in the debounce wrapper.
You do have to check if the current invocation was the leading one in order to not add the timeout in that case.
It could look like this:
const _ = require('lodash');
const bb = require('bluebird');
function handler(arg) {
console.log(arg, new Date().getSeconds());
}
const debounceWithDelay = (func, delay, postDelay) => {
let postDebounceWait;
let timeOutLeading = false;
const debounced = _.debounce((...args) => {
// wrap the handler so we can add an additional timeout to the debounce invocation
if (timeOutLeading) {
/*
for the first invocation we do not want an additional timeout.
We can know this is the leading invocation because,
we set timeOutLeading immediately to false after invoking the debounced function.
This only works because the debounced leading functionality is synchronous it self.
( aka it does not use a trampoline )
*/
func(...args);
} else {
postDebounceWait = setTimeout(() => {
func(...args)
}, postDelay);
}
}, delay, {leading: true});
return (...args) => {
// wrap the debounced method it self so we can cancel the post delay timer that was invoked by debounced on each invocation.
timeOutLeading = true;
clearTimeout(postDebounceWait);
debounced(...args);
timeOutLeading = false;
}
};
const debounceDelay = debounceWithDelay(handler, 50, 2000);
(async function () {
console.log(new Date().getSeconds());
debounceDelay(1);
debounceDelay(2);
debounceDelay(3);
debounceDelay(4);
await bb.delay(3000);
debounceDelay(5);
await bb.delay(3000);
debounceDelay(6);
debounceDelay(7);
debounceDelay(8);
})();
Runnable script:
Here's something that I think works the way you described. If not, it's at least something to go off of.
// set up the event bus
const start = getMilli()
const bus = createBus()
bus.on('event', e => console.log(`[${getPassage(start)}] [${e}] original bus: saw event`))
const wrappedBus = wrapBus(1600, 'event', bus)
wrappedBus.on('event', e => console.log(`[${getPassage(start)}] [${e}] wrapped bus: saw event`))
wrappedBus.on('skipped', e => console.log(`[${getPassage(start)}] [${e}] skipped by wrapped bus`))
wrappedBus.on('last before interval', e => console.log(`[${getPassage(start)}] [${e}] this was the last event before the end of the interval`))
wrappedBus.on('interval tick', _ => console.log(`[${getPassage(start)}] interval tick`))
// trigger events on the bus every so often
let totalTime = 0
const intervalTime = 300
setInterval(() => {
totalTime += intervalTime
bus.trigger('event', totalTime)
}, intervalTime)
function getMilli() {
return (new Date()).getTime()
}
function getPassage(from) {
return getMilli() - from
}
// creates a simple event bus
function createBus() {
const cbs = {}
return {
on: (label, cb) => {
if(cbs.hasOwnProperty(label)) cbs[label].push(cb)
else cbs[label] = [cb]
},
trigger: (label, e) => {
if(cbs.hasOwnProperty(label)) cbs[label].forEach(f => f(e))
},
}
}
// creates a new bus that should trigger the way you described
function wrapBus(waitInterval, eventLabel, bus) {
const newBus = createBus()
let deliveredFirst = false
let gotIgnoredEvent = false
let lastIgnoredEvent = undefined
setInterval(() => {
// just here so we know when this interval timer is ticking
newBus.trigger('interval tick', null)
// push the last event before the end of this interval
if(gotIgnoredEvent) {
gotIgnoredEvent = false
deliveredFirst = false
newBus.trigger(eventLabel, lastIgnoredEvent)
newBus.trigger('last before interval', lastIgnoredEvent)
}
}, waitInterval)
bus.on(eventLabel, function(e) {
if(!deliveredFirst) {
newBus.trigger(eventLabel, e)
deliveredFirst = true
gotIgnoredEvent = false
}
else {
gotIgnoredEvent = true
lastIgnoredEvent = e
// this is here just to see when the wrapped bus skipped events
newBus.trigger('skipped', e)
}
})
return newBus
}
Here's my try:
const debounceNext = (cb, delay) => {
let timer = null;
let next = null;
const runTimer = (delay, event) => {
timer = setTimeout(() => {
timer = null;
if(next) {
next(event);
next = null;
runTimer(delay);
}
}, delay);
};
return (event) => {
if(!timer) {
cb(event);
}
else {
next = cb;
clearTimeout(timer);
}
runTimer(delay, event);
}
};
const processEvent = (event) => console.log(event);
const debouncedHandler = debounceNext(processEvent, 125);
myElement.addEventListener('scroll', debouncedHandler);

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.

Categories

Resources