setTimeout
may not be as reliable as other methods like promises, which have a higher execution priority. To work around this, you can create a custom timer using promises. Here is an example:
var customDelay = new Promise(function (resolve) {
var delay = 10; // milliseconds
var before = Date.now();
while (Date.now() < before + delay) { };
resolve();
});
customDelay.then(function () {
//Timer triggered
});
Update 1:
If you need a 10ms update frequency, running the above code on the main thread can lock up the UI due to the while loop. Offloading the while loop into a web worker can solve this issue. Here is some code:
<html>
<head>
<title></title>
</head>
<body>
<script id="FastTimer" type="javascript/worker">
onmessage = function (event) {
var delay = 10; // milliseconds
var before = Date.now();
while (Date.now() < before + delay) { };
postMessage({data: []});
};
</script>
<script>
var worker;
window.onload = function() {
var blob = new Blob([document.querySelector("#FastTimer").textContent]);
blobURL = window.URL.createObjectURL(blob);
worker = new Worker(blobURL);
worker.addEventListener("message", receivedWorkerMessage);
worker.onerror = workerError;
//Start the worker.
worker.postMessage({});
}
var counter = 0;
function receivedWorkerMessage(event) {
worker.postMessage({});
timerTiggered();
}
function timerTiggered() {
counter++;
console.log(counter);
}
function workerError(error) {
alert(error.message);
}
function stopWorker() {
worker.terminate();
worker = null;
}
</script>
</body>
</html>
The downside of the above approach is that there might be a slight time cost in communication with the worker.
Typically, requestAnimationFrame
is used for animations in web apps. However, it may not trigger when the screen is locked. If you still want to try, here is a sample code:
<html>
<head>
<title></title>
</head>
<body>
<script>
var counter = 0;
var minTimeSpan = 10;
var lastTime = performance.now();
function animate() {
let t = performance.now();
if (t - lastTime >= minTimeSpan) {
timerTiggered();
}
requestAnimationFrame(animate);
}
function timerTiggered() {
counter++;
console.log(counter);
}
animate();
</script>
</body>
</html>
Update 2:
Feedback suggests that over time, Update 1 may lead to high memory usage and tabs crashing. Initially, using setTimeout
in a web worker wasn't accurate in hidden tabs in some browsers, but worked well in Chrome and Edge. Make sure to test this across your target browsers.
<html>
<head>
<title></title>
</head>
<body>
<script id="FastTimer" type="javascript/worker">
onmessage = function (event) {
var delay = 10; // milliseconds
setTimeout(() => {
postMessage({data: []});
}, delay);
};
</script>
<script>
var worker;
window.onload = function() {
var blob = new Blob([document.querySelector("#FastTimer").textContent]);
blobURL = window.URL.createObjectURL(blob);
worker = new Worker(blobURL);
worker.addEventListener("message", receivedWorkerMessage);
worker.onerror = workerError;
//Start the worker.
worker.postMessage({});
}
var counter = 0;
function receivedWorkerMessage(event) {
worker.postMessage({});
timerTiggered();
}
function timerTiggered() {
counter++;
console.log(counter);
}
function workerError(error) {
alert(error.message);
}
function stopWorker() {
worker.terminate();
worker = null;
}
</script>
</body>
</html>