segundos - ¿Existe alguna forma más precisa de crear un temporizador de JavaScript que setTimeout?
parar intervalo javascript (16)
¿Hay algún truco que se pueda hacer para garantizar que
setTimeout()
precisión (sin recurrir a una API externa) o es una causa perdida?
No y no. No vas a obtener nada cerca de un temporizador perfectamente preciso con setTimeout()
: los navegadores no están configurados para eso. Sin embargo , no es necesario que confíe en él para cronometrar las cosas tampoco. La mayoría de las librerías de animación descubrieron esto hace años: configura una devolución de llamada con setTimeout()
, pero determina qué debe hacerse en función del valor de (new Date()).milliseconds
(o equivalente). Esto le permite aprovechar la compatibilidad con el temporizador más confiable en los navegadores más nuevos, mientras se comporta de forma adecuada en los navegadores más antiguos.
¡También le permite evitar usar demasiados temporizadores ! Esto es importante: cada temporizador es una devolución de llamada. Cada devolución de llamada ejecuta el código JS. Mientras se está ejecutando el código JS, los eventos del navegador, incluidas otras devoluciones de llamadas, se retrasan o se descartan. Cuando finaliza la devolución de llamada, las devoluciones de llamada adicionales deben competir con otros eventos del navegador para tener la oportunidad de ejecutar. Por lo tanto, un temporizador que maneja todas las tareas pendientes para ese intervalo tendrá un mejor rendimiento que dos temporizadores con intervalos de coincidencia, y (para tiempos de espera cortos) ¡mejor que dos temporizadores con tiempos de espera superpuestos!
Resumen: deje de usar setTimeout()
para implementar diseños de "un temporizador / una tarea" y use el reloj en tiempo real para suavizar las acciones de la IU.
Algo que siempre me ha molestado es cuán impredecible es el método setTimeout()
en Javascript.
En mi experiencia, el cronómetro es terriblemente impreciso en muchas situaciones. Por inexacto, me refiero a que el tiempo de demora real parece variar entre 250 y 500 ms más o menos. Aunque no es una gran cantidad de tiempo, al usarlo para ocultar / mostrar elementos de la interfaz de usuario, el tiempo puede ser notable.
¿Hay algún truco que se pueda hacer para garantizar que setTimeout()
precisión (sin recurrir a una API externa) o es una causa perdida?
.
ÁRBITRO; http://www.sitepoint.com/creating-accurate-timers-in-javascript/
Este sitio me rescató en gran escala.
Puede usar el reloj del sistema para compensar la inexactitud del temporizador. Si ejecuta una función de temporización como una serie de llamadas a setTimeout, cada instancia llama al siguiente, entonces todo lo que tiene que hacer para mantenerla precisa es calcular exactamente qué tan imprecisa es y restar esa diferencia de la siguiente iteración:
var start = new Date().getTime(),
time = 0,
elapsed = ''0.0'';
function instance()
{
time += 100;
elapsed = Math.floor(time / 100) / 10;
if(Math.round(elapsed) == elapsed) { elapsed += ''.0''; }
document.title = elapsed;
var diff = (new Date().getTime() - start) - time;
window.setTimeout(instance, (100 - diff));
}
window.setTimeout(instance, 100);
Este método minimizará la deriva y reducirá las imprecisiones en más del 90%.
Solucionó mis problemas, espero que ayude
Aquí está lo que uso. Como es JavaScript, publicaré mis soluciones frontend y node.js:
Para ambos, utilizo la misma función de redondeo decimal que recomiendo que mantenga a la distancia de los brazos porque razones:
const round = (places, number) => +(Math.round(number + `e+${places}`) + `e-${places}`)
places
- Número de decimales en los cuales redondear, esto debería ser seguro y debería evitar cualquier problema con los flotadores (algunos números como 1.0000000000005 ~ pueden ser problemáticos). Pasé tiempo investigando la mejor manera de redondear los decimales proporcionados por los temporizadores de alta resolución convertidos a milisegundos.
that + symbol
- Es un operador unario que convierte un operando en un número, virtualmente idéntico al Number()
Navegador
const start = performance.now()
// I wonder how long this comment takes to parse
const end = performance.now()
const result = (end - start) + '' ms''
const adjusted = round(2, result) // see above rounding function
node.js
// Start timer
const startTimer = () => process.hrtime()
// End timer
const endTimer = (time) => {
const diff = process.hrtime(time)
const NS_PER_SEC = 1e9
const result = (diff[0] * NS_PER_SEC + diff[1])
const elapsed = Math.round((result * 0.0000010))
return elapsed
}
// This end timer converts the number from nanoseconds into milliseconds;
// you can find the nanosecond version if you need some seriously high-resolution timers.
const start = startTimer()
// I wonder how long this comment takes to parse
const end = endTimer(start)
console.log(end + '' ms'')
Aquí hay un ejemplo de la sugerencia de demo Shog9. Esto llena suavemente una barra de progreso jquery durante 6 segundos, luego redirige a una página diferente una vez que se llena:
var TOTAL_SEC = 6;
var FRAMES_PER_SEC = 60;
var percent = 0;
var startTime = new Date().getTime();
setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
function updateProgress() {
var currentTime = new Date().getTime();
// 1000 to convert to milliseconds, and 100 to convert to percentage
percent = (currentTime - startTime) / (TOTAL_SEC * 1000) * 100;
$("#progressbar").progressbar({ value: percent });
if (percent >= 100) {
window.location = "newLocation.html";
} else {
setTimeout(updateProgress, 1000 / FRAMES_PER_SEC);
}
}
Dan, desde mi experiencia (que incluye la implementación del lenguaje SMIL2.1 en JavaScript, donde la gestión del tiempo está en el tema), puedo asegurarle que nunca necesita una alta precisión de setTimeout o setInterval.
Lo que sí importa es el orden en que se ejecuta setTimeout / setInterval cuando se pone en cola, y eso siempre funciona perfectamente.
Debes "arrastrarte" en el tiempo objetivo. Algunas pruebas y errores serán necesarios, pero en esencia.
Establezca un tiempo de espera para completar alrededor de 100 ms antes del tiempo requerido
hacer que el controlador de tiempo de espera funcione así:
calculate_remaining_time
if remaining_time > 20ms // maybe as much as 50
re-queue the handler for 10ms time
else
{
while( remaining_time > 0 ) calculate_remaining_time;
do_your_thing();
re-queue the handler for 100ms before the next required time
}
Pero su ciclo while aún puede ser interrumpido por otros procesos, por lo que aún no es perfecto.
Definitivamente hay una limitación aquí. Para darle un poco de perspectiva, el navegador Chrome que acaba de lanzar Google es lo suficientemente rápido como para ejecutar setTimeout (function () {}, 0) en 15-20 ms, mientras que los antiguos motores de Javascript tardaron cientos de milisegundos en ejecutar esa función. Aunque setTimeout usa milisegundos, ninguna máquina virtual de javascript en este momento puede ejecutar código con esa precisión.
Este es un temporizador que hice para un proyecto de música mío que hace esto. Temporizador que es preciso en todos los dispositivos.
var Timer = function(){
var framebuffer = 0,
var msSinceInitialized = 0,
var timer = this;
var timeAtLastInterval = new Date().getTime();
setInterval(function(){
var frametime = new Date().getTime();
var timeElapsed = frametime - timeAtLastInterval;
msSinceInitialized += timeElapsed;
timeAtLastInterval = frametime;
},1);
this.setInterval = function(callback,timeout,arguments) {
var timeStarted = msSinceInitialized;
var interval = setInterval(function(){
var totaltimepassed = msSinceInitialized - timeStarted;
if (totaltimepassed >= timeout) {
callback(arguments);
timeStarted = msSinceInitialized;
}
},1);
return interval;
}
}
var timer = new Timer();
timer.setInterval(function(){console.log("This timer will not drift."),1000}
La respuesta de shog9 es más o menos lo que yo diría, aunque agregaría lo siguiente acerca de la animación de UI / eventos:
Si tiene una caja que se supone que debe deslizarse en la pantalla, expanda hacia abajo y luego difumine su contenido, no intente separar los tres eventos con retrasos programados para hacer que disparen uno tras otro. Use devoluciones de llamada, por lo que una vez el primer evento se hace deslizándolo, llama al expansor, una vez hecho, llama al fader. jQuery puede hacerlo fácilmente, y estoy seguro de que otras bibliotecas también pueden hacerlo.
Los tiempos de espera de JavaScript tienen un límite de facto de 10-15 ms (no estoy seguro de lo que estás haciendo para obtener 200 ms, a menos que estés haciendo 185 ms de ejecución real de js). Esto se debe a que las ventanas tienen una resolución de temporizador estándar de 15ms, la única forma de hacerlo mejor es utilizar los temporizadores de mayor resolución de Windows, que es una configuración de todo el sistema para poder atornillar con otras aplicaciones en el sistema y también reducir la duración de la batería (Chrome tiene un error de Intel en este tema).
El estándar de facto de 10-15ms se debe a que las personas usan tiempos de espera de 0ms en sitios web pero luego codifican de una manera que supone que supone un tiempo de espera de 10-15ms (por ejemplo, juegos js que asumen 60fps pero solicitan 0ms / frame sin lógica delta para que el el juego / sitio / animación va unos pocos órdenes de magnitud más rápido de lo previsto). Para dar cuenta de eso, incluso en plataformas que no tienen problemas con el temporizador de Windows, los navegadores limitan la resolución del temporizador a 10 ms.
Odio decirlo, pero no creo que haya una manera de aliviar esto. Sin embargo, creo que depende del sistema del cliente, por lo que un motor o una máquina JavaScript más rápidos pueden hacerlo un poco más preciso.
Para mi experiencia, es un esfuerzo perdido, incluso cuando la menor cantidad razonable de tiempo que he reconocido js es de alrededor de 32-33 ms. ...
Podría considerar usar el http://www.sitepoint.com/creating-accurate-timers-in-javascript/ que usa el tiempo del sistema para una mejor precisión
Si necesita obtener una devolución de llamada precisa en un intervalo determinado, esta idea puede ayudarlo a:
Tuve un problema similar no hace mucho tiempo y se me ocurrió un enfoque que combina requestAnimationFrame
con performance.now () que funciona de manera muy efectiva.
Ahora puedo hacer que los temporizadores tengan una precisión de aproximadamente 12 decimales:
window.performance = window.performance || {};
performance.now = (function() {
return performance.now ||
performance.mozNow ||
performance.msNow ||
performance.oNow ||
performance.webkitNow ||
function() {
//Doh! Crap browser!
return new Date().getTime();
};
})();
Si está utilizando setTimeout()
para ceder rápidamente al navegador para que su hilo de interfaz de usuario pueda ponerse al día con las tareas que necesita hacer (como actualizar una pestaña, o no mostrar el diálogo de Script de ejecución prolongada), hay un nuevo API llamada Efficient Script Yielding , también conocida como setImmediate()
que puede funcionar un poco mejor para usted.
setImmediate()
funciona de manera muy similar a setTimeout()
, pero puede ejecutarse inmediatamente si el navegador no tiene nada más que hacer. En muchas situaciones en las que está utilizando setTimeout(..., 16)
o setTimeout(..., 4)
o setTimeout(..., 0)
(es decir, desea que el navegador ejecute cualquier tarea de subproceso de UI pendiente y no muestre un Diálogo Script de ejecución larga), puede simplemente reemplazar su setTimeout()
con setImmediate()
, setImmediate()
el segundo argumento (milisegundos).
La diferencia con setImmediate()
es que básicamente es un rendimiento; si el navegador tiene que hacer alguna vez en el hilo de la interfaz de usuario (por ejemplo, actualizar una pestaña), lo hará antes de volver a la devolución de llamada. Sin embargo, si el navegador ya está totalmente atrapado en su trabajo, la devolución de llamada especificada en setImmediate()
esencialmente se ejecutará sin demora.
Desafortunadamente, solo se admite actualmente en IE9 +, ya que hay un retroceso de los otros proveedores de navegadores.
Sin embargo, hay un buen polyfill disponible, si desea usarlo y esperamos que los otros navegadores lo implementen en algún momento.
Si está utilizando setTimeout()
para la animación , requestAnimationFrame es su mejor requestAnimationFrame ya que su código se ejecutará en sincronización con la frecuencia de actualización del monitor.
Si está utilizando setTimeout()
con una cadencia más lenta , por ejemplo, una vez cada 300 milisegundos, podría usar una solución similar a la que sugiere el usuario1213320, donde puede controlar cuánto tiempo pasó desde la última marca de tiempo que ejecutó su temporizador y compensar cualquier retraso. Una mejora es que puede usar la nueva interfaz de tiempo de alta resolución (también window.performance.now()
como window.performance.now()
) en lugar de Date.now()
para obtener una resolución superior a milisegundos para la hora actual.