hacer - timer javascript ejemplo
¿Cómo crear un temporizador preciso en javascript? (6)
¿Por qué no es exacto?
Porque estás usando
setTimeout()
o
setInterval()
.
No se puede confiar en ellos
, no hay garantías de precisión para ellos.
Se les permite retrasarse
arbitrariamente, y no mantienen un ritmo constante, sino que
tienden a derivar
(como ha observado).
¿Cómo puedo crear un temporizador preciso?
Utilice el objeto
Date
lugar para obtener la hora actual exacta (milisegundos).
Luego, base su lógica en el valor de tiempo actual, en lugar de contar con qué frecuencia se ha ejecutado su devolución de llamada.
Para un temporizador o reloj simple, realice un seguimiento explícito de la diferencia horaria:
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
Ahora, eso tiene el problema de posiblemente saltar valores.
Cuando el intervalo se demora un poco y ejecuta su devolución de llamada después de
990
,
1993
,
2996
,
3999
,
5002
milisegundos, verá el segundo recuento
0
,
1
,
2
,
3
,
5
(!).
Por lo tanto, sería recomendable actualizar más a menudo, como cada 100 ms, para evitar tales saltos.
Sin embargo, a veces realmente necesita un intervalo constante para ejecutar sus devoluciones de llamada sin deriva. Esto requiere una estrategia un poco más ventajosa (y código), aunque paga bien (y registra menos tiempos de espera). Esos se conocen como temporizadores autoajustables . Aquí el retraso exacto para cada uno de los tiempos de espera repetidos se adapta al tiempo realmente transcurrido, en comparación con los intervalos esperados:
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
}
Necesito crear un temporizador simple pero preciso.
Este es mi código:
var seconds = 0;
setInterval(function() {
timer.innerHTML = seconds++;
}, 1000);
Después de exactamente 3600 segundos, imprime aproximadamente 3500 segundos.
-
¿Por qué no es exacto?
-
¿Cómo puedo crear un temporizador preciso?
Esta es una vieja pregunta, pero pensé que compartiría el código que uso a veces:
function Timer(func, delay, repeat, runAtStart)
{
this.func = func;
this.delay = delay;
this.repeat = repeat || 0;
this.runAtStart = runAtStart;
this.count = 0;
this.startTime = performance.now();
if (this.runAtStart)
this.tick();
else
{
var _this = this;
this.timeout = window.setTimeout( function(){ _this.tick(); }, this.delay);
}
}
Timer.prototype.tick = function()
{
this.func();
this.count++;
if (this.repeat === -1 || (this.repeat > 0 && this.count < this.repeat) )
{
var adjustedDelay = Math.max( 1, this.startTime + ( (this.count+(this.runAtStart ? 2 : 1)) * this.delay ) - performance.now() );
var _this = this;
this.timeout = window.setTimeout( function(){ _this.tick(); }, adjustedDelay);
}
}
Timer.prototype.stop = function()
{
window.clearTimeout(this.timeout);
}
Ejemplo:
time = 0;
this.gameTimer = new Timer( function() { time++; }, 1000, -1);
setTimeout
el
setTimeout
, puede ejecutarlo X número de veces (-1 para infinito), puede comenzar a ejecutarse instantáneamente y tiene un contador si alguna vez necesita ver cuántas veces se ha ejecutado el
func()
.
Viene muy bien.
Editar: Tenga en cuenta que esto no realiza ninguna comprobación de entrada (como si el retraso y la repetición son del tipo correcto. Y probablemente desee agregar algún tipo de función get / set si desea obtener el recuento o cambiar el valor de repetición .
Estoy de acuerdo con Bergi en usar Date, pero su solución fue un poco exagerada para mi uso. Simplemente quería que mi reloj animado (SVG digitales y analógicos) se actualizara en el segundo y que no se desbordara ni se ejecutara creando saltos obvios en las actualizaciones del reloj. Aquí está el fragmento de código que puse en las funciones de actualización de mi reloj:
var milliseconds = now.getMilliseconds();
var newTimeout = 1000 - milliseconds;
this.timeoutVariable = setTimeout((function(thisObj) { return function() { thisObj.update(); } })(this), newTimeout);
Simplemente calcula el tiempo delta al siguiente segundo par y establece el tiempo de espera a ese delta. Esto sincroniza todos mis objetos de reloj con el segundo. Espero que esto sea útil.
La mayoría de los temporizadores en las respuestas aquí se retrasarán el tiempo esperado porque establecen el valor "esperado" en el ideal y solo explican el retraso que introdujo el navegador antes de ese punto. Esto está bien si solo necesita intervalos precisos, pero si está cronometrando en relación con otros eventos, entonces (casi) siempre tendrá este retraso.
Para corregirlo, puede realizar un seguimiento del historial de deriva y utilizarlo para predecir la deriva futura. Al agregar un ajuste secundario con esta corrección preventiva, la variación en la deriva se centra alrededor del tiempo objetivo. Por ejemplo, si siempre obtiene una deriva de 20 a 40 ms, este ajuste lo desplazaría a -10 a + 10 ms alrededor del tiempo objetivo.
Sobre la base de la respuesta de Bergi , he usado una mediana variable para mi algoritmo de predicción. Tomar solo 10 muestras con este método hace una diferencia razonable.
var interval = 200; // ms
var expected = Date.now() + interval;
var drift_history = [];
var drift_history_samples = 10;
var drift_correction = 0;
function calc_drift(arr){
// Calculate drift correction.
/*
In this example I''ve used a simple median.
You can use other methods, but it''s important not to use an average.
If the user switches tabs and back, an average would put far too much
weight on the outlier.
*/
var values = arr.concat(); // copy array so it isn''t mutated
values.sort(function(a,b){
return a-b;
});
if(values.length ===0) return 0;
var half = Math.floor(values.length / 2);
if (values.length % 2) return values[half];
var median = (values[half - 1] + values[half]) / 2.0;
return median;
}
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
// don''t update the history for exceptionally large values
if (dt <= interval) {
// sample drift amount to history after removing current correction
// (add to remove because the correction is applied by subtraction)
drift_history.push(dt + drift_correction);
// predict new drift correction
drift_correction = calc_drift(drift_history);
// cap and refresh samples
if (drift_history.length >= drift_history_samples) {
drift_history.shift();
}
}
expected += interval;
// take into account drift with prediction
setTimeout(step, Math.max(0, interval - dt - drift_correction));
}
No se vuelve mucho más preciso que esto.
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);
En cuanto a por qué no es preciso, supongo que la máquina está ocupada haciendo otras cosas, disminuyendo un poco cada vez que se suma la iteración, como puede ver.
Solo construyo un poco
la respuesta de Bergi
(específicamente la segunda parte) porque realmente me gustó la forma en que se hizo, pero quiero la opción de detener el temporizador una vez que comienza (como
clearInterval()
casi).
Entonces ... Lo he incluido en una función de constructor para que podamos hacer cosas ''objetivas'' con él.
1. Constructor
Muy bien, entonces copie / pegue eso ...
/**
* 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. Instanciar
Dile qué hacer y todo eso ...
// 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. Entonces haz ... cosas
// 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;
Quiero decir, funciona para mí de todos modos. Si hay una mejor manera, déjame saber.