Parece que requestAnimationFrame
es la forma de facto de animar las cosas ahora. Funcionó bastante bien para mí en su mayor parte, pero en este momento estoy tratando de hacer algunas animaciones de lienzo y me preguntaba: ¿hay alguna manera de asegurarme de que funcione a ciertos fps? Entiendo que el propósito de rAF es para animaciones consistentemente suaves, y podría correr el riesgo de hacer que mi animación sea entrecortada, pero en este momento parece funcionar a velocidades drásticamente diferentes de manera bastante arbitraria, y me pregunto si hay una manera de combatir que de alguna manera
Lo usaría setInterval
pero quiero las optimizaciones que ofrece rAF (especialmente parando automáticamente cuando la pestaña está enfocada).
En caso de que alguien quiera ver mi código, es más o menos:
animateFlash: function() {
ctx_fg.clearRect(0,0,canvasWidth,canvasHeight);
ctx_fg.fillStyle = 'rgba(177,39,116,1)';
ctx_fg.strokeStyle = 'none';
ctx_fg.beginPath();
for(var i in nodes) {
nodes[i].drawFlash();
}
ctx_fg.fill();
ctx_fg.closePath();
var instance = this;
var rafID = requestAnimationFrame(function(){
instance.animateFlash();
})
var unfinishedNodes = nodes.filter(function(elem){
return elem.timer < timerMax;
});
if(unfinishedNodes.length === 0) {
console.log("done");
cancelAnimationFrame(rafID);
instance.animate();
}
}
Donde Node.drawFlash () es solo un código que determina el radio basado en una variable de contador y luego dibuja un círculo.
fuente
requestAnimationFrame
es (como su nombre indica) solicitar un cuadro de animación solo cuando es necesario. Digamos que muestra un lienzo negro estático, debería obtener 0 fps porque no se necesita un nuevo marco. Pero si está mostrando una animación que requiere 60 fps, también debería obtenerla.rAF
simplemente permite "omitir" tramas inútiles y luego guardar la CPU.Respuestas:
Cómo estrangular requestAnimationFrame a una velocidad de fotogramas específica
Aceleración de demostración a 5 FPS: http://jsfiddle.net/m1erickson/CtsY3/
Este método funciona probando el tiempo transcurrido desde la ejecución del último bucle de cuadro.
Su código de dibujo se ejecuta solo cuando ha transcurrido el intervalo FPS especificado.
La primera parte del código establece algunas variables utilizadas para calcular el tiempo transcurrido.
Y este código es el bucle requestAnimationFrame real que se basa en su FPS especificado.
fuente
Actualización 2016/6
El problema que limita la velocidad de fotogramas es que la pantalla tiene una velocidad de actualización constante, típicamente 60 FPS.
Si queremos 24 FPS, nunca obtendremos los verdaderos 24 fps en la pantalla, podemos cronometrarlo como tal, pero no mostrarlo, ya que el monitor solo puede mostrar cuadros sincronizados a 15 fps, 30 fps o 60 fps (algunos monitores también 120 fps )
Sin embargo, para fines de tiempo podemos calcular y actualizar cuando sea posible.
Puede construir toda la lógica para controlar la velocidad de cuadros encapsulando cálculos y devoluciones de llamada en un objeto:
Luego agregue un código de controlador y configuración:
Uso
Se vuelve muy simple: ahora, todo lo que tenemos que hacer es crear una instancia configurando la función de devolución de llamada y la velocidad de fotogramas deseada así:
Luego, comience (que podría ser el comportamiento predeterminado si lo desea):
Eso es todo, toda la lógica se maneja internamente.
Manifestación
Vieja respuesta
El objetivo principal de
requestAnimationFrame
es sincronizar las actualizaciones a la frecuencia de actualización del monitor. Esto requerirá que se anime en el FPS del monitor o en un factor del mismo (es decir, 60, 30, 15 FPS para una frecuencia de actualización típica a 60 Hz).Si desea un FPS más arbitrario, entonces no tiene sentido usar rAF ya que la frecuencia de cuadros nunca coincidirá con la frecuencia de actualización del monitor de todos modos (solo un cuadro aquí y allá) que simplemente no puede proporcionarle una animación uniforme (como con todas las repeticiones de cuadros) ) y también puede usar
setTimeout
o en susetInterval
lugar.Este también es un problema bien conocido en la industria del video profesional cuando desea reproducir un video en un FPS diferente del dispositivo que muestra la actualización en. Se han utilizado muchas técnicas, como la combinación de cuadros y la re-sincronización compleja, la reconstrucción de cuadros intermedios basados en vectores de movimiento, pero con el lienzo estas técnicas no están disponibles y el resultado siempre será un video desigual.
La razón por la que colocamos
setTimeout
primero (y por qué algún lugarrAF
primero cuando se usa un relleno de polietileno) es que esto será más preciso ya quesetTimeout
pondrá en cola un evento inmediatamente cuando se inicie el ciclo, de modo que no importa cuánto tiempo usará el código restante (siempre que no exceda el intervalo de tiempo de espera) la próxima llamada será en el intervalo que representa (para rAF puro esto no es esencial ya que rAF intentará saltar al siguiente marco en cualquier caso).También vale la pena señalar que colocarlo primero también correrá el riesgo de que las llamadas se acumulen como con
setInterval
.setInterval
puede ser un poco más preciso para este uso.Y puede usar
setInterval
en su lugar fuera del bucle para hacer lo mismo.Y para detener el ciclo:
Para reducir la velocidad de fotogramas cuando la pestaña se vuelve borrosa, puede agregar un factor como este:
De esta manera, puede reducir el FPS a 1/4, etc.
fuente
requestAnimationFrame
es sincronizar las operaciones del DOM (lectura / escritura) para que no usarlo perjudique el rendimiento al acceder al DOM, ya que las operaciones no se pondrán en cola para que se realicen juntas y forzarán el repintado del diseño innecesariamente.Sugiero finalizar su llamada
requestAnimationFrame
en unsetTimeout
:Debe llamar
requestAnimationFrame
desde dentrosetTimeout
, en lugar de al revés, porquerequestAnimationFrame
programa su función para que se ejecute justo antes del próximo repintado, y si retrasa su actualización aún mássetTimeout
, habrá perdido esa ventana de tiempo. Sin embargo, hacer lo contrario es correcto, ya que simplemente está esperando un período de tiempo antes de realizar la solicitud.fuente
Todas estas son buenas ideas en teoría, hasta que profundices. El problema es que no se puede estrangular un RAF sin desincronizarlo, lo que frustra su propósito de existir. ¡Entonces lo deja correr a toda velocidad y actualiza sus datos en un bucle separado , o incluso en un hilo separado!
Sí lo dije. Usted puede hacer de múltiples hebras de JavaScript en el navegador!
Sé que hay dos métodos que funcionan extremadamente bien sin jank, usando mucho menos jugo y creando menos calor. El resultado neto es el tiempo preciso a escala humana y la eficiencia de la máquina.
Disculpas si esto es un poco prolijo, pero aquí va ...
Método 1: Actualice los datos a través de setInterval y los gráficos a través de RAF.
Use un setInterval separado para actualizar los valores de traslación y rotación, física, colisiones, etc. Mantenga esos valores en un objeto para cada elemento animado. Asigne la cadena de transformación a una variable en el objeto cada 'marco' setInterval. Mantenga estos objetos en una matriz. Establezca su intervalo a sus fps deseados en ms: ms = (1000 / fps). Esto mantiene un reloj constante que permite los mismos fps en cualquier dispositivo, independientemente de la velocidad RAF. ¡No asigne las transformaciones a los elementos aquí!
En un bucle requestAnimationFrame, recorra su matriz con un bucle for de la vieja escuela; no use los formularios más nuevos aquí, ¡son lentos!
En su función rafUpdate, obtenga la cadena de transformación de su objeto js en la matriz y su id de elementos. Ya debería tener sus elementos 'sprite' unidos a una variable o de fácil acceso a través de otros medios para que no pierda tiempo 'metiéndolos' en la RAF. Mantenerlos en un objeto con el nombre de su identificación html funciona bastante bien. Configure esa parte incluso antes de que entre en su SI o RAF.
Use la RAF para actualizar solo sus transformaciones , use solo transformaciones 3D (incluso para 2d) y configure css "will-change: transform;" en elementos que cambiarán. Esto mantiene sus transformaciones sincronizadas con la frecuencia de actualización nativa tanto como sea posible, activa la GPU y le dice al navegador dónde concentrarse más.
Entonces deberías tener algo como este pseudocódigo ...
Esto mantiene sus actualizaciones de los objetos de datos y las cadenas de transformación sincronizadas con la frecuencia de 'fotograma' deseada en el SI, y las asignaciones de transformación reales en la RAF sincronizadas con la frecuencia de actualización de la GPU. Por lo tanto, las actualizaciones de gráficos reales solo están en la RAF, pero los cambios en los datos y la construcción de la cadena de transformación están en el SI, por lo tanto, no hay jankies sino que el 'tiempo' fluye a la velocidad de fotogramas deseada.
Fluir:
Método 2. Ponga el SI en un trabajador web. ¡Este es FAAAST y suave!
Igual que el método 1, pero coloca el SI en web-worker. Se ejecutará en un hilo totalmente separado, dejando la página para tratar solo con la RAF y la interfaz de usuario. Pase el conjunto de sprites de ida y vuelta como un 'objeto transferible'. Esto es muy rápido. No toma tiempo clonar o serializar, pero no es como pasar por referencia, ya que la referencia del otro lado se destruye, por lo que deberá hacer que ambos lados pasen al otro lado y solo actualizarlos cuando estén presentes, ordenar de pasar una nota de ida y vuelta con tu novia en la escuela secundaria.
Solo uno puede leer y escribir a la vez. Esto está bien siempre que verifiquen si no está indefinido para evitar un error. El RAF es RÁPIDO y lo devolverá de inmediato, luego pasará por un montón de marcos de GPU solo para verificar si ya se ha enviado de vuelta. El SI en el web-worker tendrá la matriz de sprites la mayor parte del tiempo y actualizará los datos de posición, movimiento y física, así como creará la nueva cadena de transformación y luego la devolverá a la RAF en la página.
Esta es la forma más rápida que conozco para animar elementos mediante un script. Las dos funciones se ejecutarán como dos programas separados, en dos subprocesos separados, aprovechando las CPU de varios núcleos de una manera que un solo script js no lo hace. Animación javascript multihilo.
Y lo hará sin problemas sin jank, pero a la velocidad de cuadro especificada real, con muy poca divergencia.
Resultado:
Cualquiera de estos dos métodos asegurará que su script se ejecute a la misma velocidad en cualquier PC, teléfono, tableta, etc. (dentro de las capacidades del dispositivo y el navegador, por supuesto).
fuente
visibilitychange
evento.Cómo acelerar fácilmente a un FPS específico:
Fuente: Una explicación detallada de JavaScript Game Loops and Timing por Isaac Sukin
fuente
Saltar requestAnimationFrame causa una animación no uniforme (deseada) en fps personalizados.
Código original de @tavnab.
fuente
fuente
Siempre lo hago de esta manera muy simple sin meterme con las marcas de tiempo:
fuente
Aquí hay una buena explicación que encontré: CreativeJS.com , para envolver una llamada setTimeou) dentro de la función pasada a requestAnimationFrame. Mi preocupación con una solicitud "simple" Marco de animación sería: "¿y si solo quiero que se anime tres veces por segundo?" Incluso con requestAnimationFrame (en oposición a setTimeout) es que todavía desperdicia (alguna) cantidad de "energía" (lo que significa que el código del navegador está haciendo algo, y posiblemente ralentizando el sistema) 60 o 120 o tantas veces por segundo, como en oposición a solo dos o tres veces por segundo (como desee).
La mayoría de las veces ejecuto mis navegadores con JavaScript desactivado por esta razón. Pero estoy usando Yosemite 10.10.3, y creo que hay algún tipo de problema con el temporizador, al menos en mi sistema anterior (relativamente antiguo, lo que significa 2011).
fuente