Come creare un timer preciso in javascript?

Devo creare un timer semplice ma accurato.

Questo è il mio codice:

var seconds = 0; setInterval(function() { timer.innerHTML = seconds++; }, 1000); 

Dopo esattamente 3600 secondi, stampa circa 3500 secondi.

Perché non è preciso?

Perché stai usando setTimeout() o setInterval() . Non ci si può fidare , non ci sono garanzie di accuratezza per loro. Sono autorizzati a rimanere in ritardo arbitrario, e non mantengono un ritmo costante ma tendono ad andare alla deriva (come avete osservato).

Come posso creare un timer preciso?

Utilizzare invece l’object Date per ottenere l’ora corrente (millisecondo) precisa. Quindi basare la logica sul valore temporale corrente, invece di contare la frequenza con cui è stata eseguita la richiamata.

Per un semplice timer o orologio, tenere traccia della differenza di orario in modo esplicito:

 var start = Date.now(); setInterval(function() { var delta = Date.now() - start; // milliseconds elapsed since start … output(Math.floor(delta / 1000)); // in seconds // alternatively just show wall clock time: output(new Date().toUTCString()); }, 1000); // update about every second 

Ora, questo ha il problema di possibilmente saltare i valori. Quando l’intervallo è in ritardo e viene eseguita la richiamata dopo il 990 , 1993 , 2996 , 3999 , 5002 millisecondi, verrà visualizzato il secondo conteggio 0 , 1 , 2 , 3 , 5 (!). Quindi sarebbe consigliabile aggiornare più spesso, come circa ogni 100ms, per evitare tali salti.

Tuttavia, a volte hai davvero bisogno di un intervallo costante per eseguire le tue richiamate senza andare alla deriva. Ciò richiede una strategia un po ‘più avvantaggiata (e codice), anche se paga bene (e registra meno timeout). Quelli sono noti come timer auto-regolanti . Qui il ritardo esatto per ciascuno dei timeout ripetuti è adattato al tempo effettivamente trascorso, rispetto agli intervalli attesi:

 var interval = 1000; // ms var expected = Date.now() + interval; setTimeout(step, interval); function step() { var dt = Date.now() - expected; // the drift (positive for overshooting) if (dt > interval) { // something really bad happened. Maybe the browser (tab) was inactive? // possibly special handling to avoid futile "catch up" run } … // do what is to be done expected += interval; setTimeout(step, Math.max(0, interval - dt)); // take into account drift } 

Ho appena costruito la risposta di Bergi (in particolare la seconda parte) un po ‘perché mi è piaciuto molto come è stato fatto, ma voglio l’opzione di fermare il timer una volta clearInterval() come clearInterval() quasi). Sooo … L’ho trasformato in una funzione di costruzione in modo che possiamo fare cose “oggettive” con esso.

1. Costruttore

Bene, quindi copia / incolla che …

 /** * Self-adjusting interval to account for drifting * * @param {function} workFunc Callback containing the work to be done * for each interval * @param {int} interval Interval speed (in milliseconds) - This * @param {function} errorFunc (Optional) Callback to run if the drift * exceeds interval */ function AdjustingInterval(workFunc, interval, errorFunc) { var that = this; var expected, timeout; this.interval = interval; this.start = function() { expected = Date.now() + this.interval; timeout = setTimeout(step, this.interval); } this.stop = function() { clearTimeout(timeout); } function step() { var drift = Date.now() - expected; if (drift > that.interval) { // You could have some default stuff here too... if (errorFunc) errorFunc(); } workFunc(); expected += that.interval; timeout = setTimeout(step, Math.max(0, that.interval-drift)); } } 

2. Istanziare

Dillo cosa fare e tutto questo …

 // For testing purposes, we'll just increment // this and send it out to the console. var justSomeNumber = 0; // Define the work to be done var doWork = function() { console.log(++justSomeNumber); }; // Define what to do if something goes wrong var doError = function() { console.warn('The drift exceeded the interval.'); }; // (The third argument is optional) var ticker = new AdjustingInterval(doWork, 1000, doError); 

3. Quindi fare … cose

 // You can start or stop your timer at will ticker.start(); ticker.stop(); // You can also change the interval while it's in progress ticker.interval = 99; 

Voglio dire, funziona comunque per me. Se c’è un modo migliore, fammi sapere.

Non diventa molto più preciso di questo.

 var seconds = new Date().getTime(), last = seconds, intrvl = setInterval(function() { var now = new Date().getTime(); if(now - last > 5){ if(confirm("Delay registered, terminate?")){ clearInterval(intrvl); return; } } last = now; timer.innerHTML = now - seconds; }, 333); 

Per quanto riguarda il motivo per cui non è accurato, direi che la macchina è impegnata a fare altre cose, rallentando un po ‘su ogni iterazione, come si vede.

Sono d’accordo con Bergi sull’uso di Date, ma la sua soluzione è stata un po ‘eccessiva per il mio uso. Volevo semplicemente che il mio orologio animato (SVG digitale e analogico) si aggiornasse sul secondo e non sovraccarico o in esecuzione creando salti evidenti negli aggiornamenti dell’orologio. Ecco lo snippet di codice che ho inserito nelle funzioni di aggiornamento dell’orologio:

  var milliseconds = now.getMilliseconds(); var newTimeout = 1000 - milliseconds; this.timeoutVariable = setTimeout((function(thisObj) { return function() { thisObj.update(); } })(this), newTimeout); 

Semplicemente calcola il tempo di delta al secondo di secondo successivo e imposta il timeout su quel delta. Questo sincronizza tutti gli oggetti del mio orologio al secondo. Spero che questo sia utile.