Consider this C# question: Countdown timer increase on interaction?
I am in need of an equivalent for Javascript. That is, the following:
Problem
I need a callback to be called after some time T however if user interaction happens before the callback is executed then this time T must be increased by some number X. How is this modelled using Javascript?
Background (the why)
I have a page forward button which upon being clicked increases the page by 1. Increasing the page causes some hefty computations to happen so it'd be preferable to only switch page after some small time frame so that a user that's spamming the page button won't make the programs perf. go in the bin.
If I understand correctly, you can simply clear the timeout and set it again.
clearTimeout(timer);
timer = setTimeout(function(){}, 1000);
I made a quick codepen with an example. I hope it helps: https://codepen.io/daniti/pen/gjePdo
You can use a debounce function. It sets a timer and if an interation happens before timer expiration it deletes the old timer and creates a new one, effectively resets the time. Example:
function debounce(fn, delay) {
let timerId;
return function (...args) {
if (timerId)
clearTimeout(timerId);
timerId = setTimeout(() => {
fn(...args);
timerId = null;
}, delay);
}
}
You can use a timeout : https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout
document.getElementById("spamMe").onclick = doLongAction;
var canDoLongAction = true;
var timeout;
function doLongAction() {
if (canDoLongAction) {
// prevent relaunch of action
canDoLongAction = false
// the action will become available in 2sec
timeout = window.setTimeout(
() => {canDoLongAction = true},
2000
)
// you do your long action
alert("hey")
} else {
// if clicked while the timeout is going reset it
window.clearTimeout(timeout);
timeout = window.setTimeout(
() => {canDoLongAction = true},
2000
)
}
}
<button id="spamMe">spam me!!</button>
in the example, the button is blocked until you stop clicking for 2 seconds
Related
I was wondering if there is a nicer object oriented way of creating this timer? (without global vars!)
let secondsPassed = 0;
let timerId;
function startTimer() {
clearInterval(timerId);
timerId = setInterval(function() {
const seconds = twoDigits((Math.floor(secondsPassed )) % 60);
const minutes = twoDigits(Math.floor(secondsPassed / 60) % 60);
const hours = Math.floor(secondsPassed / 60 / 60);
$('#timer').text(`${hours}:${minutes}:${seconds}`);
secondsPassed++;
}, 1000);
$(window).blur(function() {
clearInterval(timerId) // stop timer when user leaves tab
});
$(window).focus(function() {
startTimer(); // continue timer when user comes back
});
}
Your current implementation is actually wrong. Every time you call startTimer, it installs startTimer as a new window focus event handler, leading to multiple started intervals when you focus the window the second time; growing exponentially. The onfocus handler should only run the timerId = setInterval(…) line - put that in a nested helper function to call only that.
This also makes it unnecessary to declare the variables globally.
function createTimer() {
let secondsPassed = 0;
let timerId;
function resume() {
if (timerId) return; // prevent multiple intervals running at the same time
timerId = setInterval(() => {
const seconds = twoDigits((Math.floor(secondsPassed )) % 60);
const minutes = twoDigits(Math.floor(secondsPassed / 60) % 60);
const hours = Math.floor(secondsPassed / 60 / 60);
$('#timer').text(`${hours}:${minutes}:${seconds}`);
secondsPassed++;
}, 1000);
}
function pause() {
clearInterval(timerId);
timerId = undefined;
}
$(window).blur(pause); // stop timer when user leaves tab
$(window).focus(resume); // continue timer when user comes back
resume(); // now start the timer
}
Now how to make that object-oriented? Just return an object from createTimer. Put resume and pause as methods on that object. Maybe add some more methods for starting, stopping, resetting, whatever you need. Maybe use a property on the object instead of the secondsPassed local variable. Or expose the local variable using a getter.
And to make it reusable, of course you can make createTimer accept arguments, from the selector of the output element, to the output element itself, to a callback function that will be called with the current time on every tick.
Edit: With this answer, you have to implement the Timer class yourself first. The code only shows how you could name the methods of the timer, how you create the instance and call its functions. The timer should (principle "separation of concerns") only handle the counting and provide the functionalities needed, like starting and stopping.
If you want to have an OOP solution for your timer, you shouldn't let the Timer class know the ID of the DOM container (like one of your comments to your question suggested).
You should read into the topic using this:
https://appdividend.com/2019/05/22/javascript-class-example-how-to-use-class-in-javascript-tutorial/
Let us assume, that you already implemented the class. Your code above should look like the following:
// Create own scope for the function, so that variable are not assigned to windows-object.
(function() {
let secondsPassed = 0;
let timer = new Timer();
// events, if necessary
timer.onTick((seconds) => { secondsPassed = seconds });
timer.onStop(() => { secondsPassed = 0; })
// Called by a button
function startTimer() {
timer.start();
}
// Example: Display alert with current timer seconds on click
function displaySecondsOfTimer() {
alert(timer.getSeconds());
}
$(window).blur(function() {
timer.stop(); // stop timer when user leaves tab
});
$(window).focus(function() {
timer.start(); // continue timer when user comes back
});
})();
So I think, you have a good example to code your first Timer class in native JavaScript! :)
Im developing a game in JavaScript in which the user needs to give a key input (press spacebar) when a clock hand moves slightly more than usual.
Currently, I am using a setTimeout function that gives the user 1 second to give a key input after the clock hand has ticked (rotated by 10 degrees).
If the user correctly presses space when the clock hand moves more than usual (15 degrees), an indicator will flash green, otherwise it will flash red.
The problem I am running into is that once the user gives an input within 1 second of the hand moving, the indicator will not flash until AFTER that 1 second has passed (ie, if the user gives an input after 0.4 seconds, the indicator will not flash until 0.6 later)
I know this is because the indicator is set up in my setTimeout fuction, which will only execute the code after 1 second. I have tried to test for the user input outside of the setTimeout function but that way the user does not get 1 second to give a response.
I was wondering if there is a way around this problem or a better way to approach this?
//Get input after clock tick
setTimeout(() => {
if (irregular_tick && space_pressed) {
flashScreenGreen();
}
if (!(space_pressed) && irregular_tick) {
flashScreenRed();
}
},1000);
Thanks for any help!
You'll need to keep a reference to your timer outside of the setTimeout callback and add a listener for the keypress with an interrupt callback which will clear the timeout if all conditions are met.
let timer = null;
let space_pressed = false;
function reset() {
timer = null;
space_pressed = false;
}
function interruptHandler(e) {
if (timer !== null) { // only look for spacebar if timer is running
space_pressed = e.key === ' ';
if (irregular_tick && space_pressed) {
// clear timeOut if successful
clearTimeout(timer);
reset();
flashScreenGreen();
}
}
}
document.body.addEventListener('keyup', interruptHandler);
timer = setTimeout(() => {
if (!space_pressed && irregular_tick) {
flashScreenRed();
}
// reset timer at end of callback ready for next run
reset();
}, 1000);
As a side note it looks like you've defined two separate flashScreenGreen() and flashScreenRed() functions. I'm guessing that they have similar if not identical logic. If that is the case you might want to consider defining a single utility flashScreen() function which accepts a color as a parameter.
function flashScreen(color) {
// logic utilizing 'color'
}
// usage
flashScreen('green');
flashScreen('#FF0000'); // red as hex
I think the clearTimeout function will help you here
// Hold the reference to the timer
const timeoutId = setTimeout(() => {
if (irregular_tick && space_pressed) {
flashScreenGreen();
//You can use the clearTimeout function to end the timer
clearTimeout(timeoutId);
}
if (!(space_pressed) && irregular_tick) {
flashScreenRed();
//clear timeout, if you need it here too
clearTimeout(timeoutId);
}
},1000);
I am having some task that is executing, and when it finishes I show an animation and redirect the user to another page. I am using setTimeout method when redirecting to another page to wait for the whole animation to finish. However, if the user goes to another tab and the event that triggers the animation and redirection fires the user goes back to tab, but doesn't see the animation. Is there any way to tell the setTimeout method to wait until that tab is in active again?
No, but you can capture the timer id you get from setTimeout as well as the clock time you started that timer (e.g. using Date.now()), then call clearTimeout for that id when the document blur event triggers, recording what the remaining time should be based on the current time and the time you saved when you created the timeout, and when the document focus event triggers, start a new timeout with that remaining time as timeout value.
E.g. something like this (but obviously, you want to turn this into something better, with some administrative functions instead of inlining everything)
const timerData = {
interval: DEFAULT_INTERVAL,
start: Date.now(),
id: setTimout(..., DEFAULT_INTERVAL)
};
document.addEventListener(`blur`, () => {
clearTimeout(timerData.id);
timerData.remaining = timerData.start + timerData.interval - Date.now();
});
document.addEventListener(`focus`, () => {
if (timerData.remaining) {
timerData.interval = timerData.remaining
timerData.remaining = undefined;
timerData.start = Date.now();
timerData.id = setTimeout(..., timerData.interval);
}
});
You can create a Timeout class with a start/stop API. Then you can instantiate the class somewhere in your code, and after start or stop it based on user interface events.
Class
class Timeout {
constructor(duration, callback) {
this.duration = duration
this.callback = callback
}
get timeout() { return this._timeout }
set timeout(timeout) { this._timeout = timeout }
get duration() { return this._duration }
set duration(duration) { this._duration = duration }
get callback() { return this._callback }
set callback(callback) { this._callback = callback }
start() {
this.timeout = setTimeout(this.callback, this.duration)
return this
}
stop() {
clearTimeout(this.timeout)
return this
}
}
Instantiation
let timeout = new Timeout(
1000,
() => {
console.log('timeout')
}
)
Start Timeout
timeout.start()
Stop Timeout
timeout.stop()
Maybe my question is silly, but I did not find any answer about it.
I have a timer of 3 seconds-
this.timer = timer(3000, 3000);
this.timerObservable = this.timer.subscribe(x => {
//do someting
});
If I am in progress of executing a very long function (for example- after 2 seconds from the previos timer I have pressed on a button the execute a lot of logic or something like it), what will happen after a minute (3 seconds from the previos timer)?
Does the long function will pause, the function inside the timer will be executed and then the long function will get control again, or maybe the long function will be fully executed and then the timer (although it will be after more than 3 seconds)?
I found I simple way to check it:
ngOnInit() {
this.timer = timer(1000, 1000);
this.timerSubscription = this.timer.subscribe(x => {
console.log("inside timer");
}
});
while (true){
console.log("in while");
}
}
The 'inside timer' does not get called..
I want to create a function that starts a timeout, but if the function is called again, before the timer ends, cancel the original call and start the timer again.
I thought I could do:
function setTimer() {
setTimeout(() => {
// do something
}, 3000)
}
...but that doesn't work, for every time I run setTimer(), it doesn't cancel the original call.
Can anyone point me in the right direction?
setTimeout returns an id you can use to clear that timeout with clearTimeout(). So you can clear the existing timeout at the beginning of your function.
For example if you keep clicking it will keep restarting -- if you don't click it finishes in 2 seconds:
let timerID;
function setTimer() {
console.log("starting/restarting timer")
clearTimeout(timerID)
timerID = setTimeout(() => {
console.log("finished")
}, 2000)
}
<p onclick="setTimer()">click to start</p>
What you want to do is cancel the existing timeout and start it over? You can do this by using cleartimeout
let timeoutFunctionVar = null;
const setTimeoutFunction = () => {
clearTimeout(timeoutFunctionVar)
timeoutFunctionVar = setTimeout(() => {
// do something
}, 3000)
};
setTimeoutFunction()
So every time setTimeoutFunction() gets called, the previous timeout gets reset
I figured this question gets asked frequently, especially for searches triggered by key events, but I couldn't find any.
The basic idea is that you keep the timeout id stateful, so you can clear it on subsequent invocations to the TO setter:
const MS_IN_SEC = 1000;
let old_timeout;
function TO_setter(searchString) {
if (old_timeout)
window.clearTimeout(old_timeout);
old_timeout = window.setTimeout(search, 2 * MS_IN_SEC, searchString);
}
function search(s) {
console.log('search for: %s', s);
}