Canvas HTML5: animaciones II: setAnimationRequest


       En la última entrada sobre animación en el canvas veíamos cómo hacer una animación usando setTimeout. Es un método poco eficiente, y en el que tenemos que elegir a priori el espaciado entre cada paso de la animación.

       Se ha añadido en HTML5 un método javascript que se encarga automáticamente de llamar a un método que deseemos antes de pintar la pantalla, de esta manera podemos realizar labores de animación justo antes de que se redibuje, siendo la forma en la que trabajan la mayor parte de programas de animación, y con la que animadores están familiarizados.

       La función es requestAnimationFrame y el uso básico sería:

function pintado (){
     requestAnimationFrame(pintado);
     // Código de la animación
}

       Estamos pidiendo cada vez que movemos un poco la animación que la próxima vez que pinte vuelva a llamar al mismo método para seguir animando.

       Los beneficios de usar este método, es despreocuparnos de calcular el tiempo entre frames, además no es un temporizador, sino que funciona de una forma más natural y nativa en el navegador, por lo que es más eficiente.

       Otra ventaja es que las pestañas inactivas no se redibujan. Si hacemos una animación con setTimeout se va a seguir ejecutando, aunque nos hayamos ido a otra pestaña, por lo que estamos haciendo cálculos que no se ven. Sin embargo si usamos requestAnimationFrame al ir a otra pestaña la animación se pausa y no hace cálculos hasta que no volvemos a esa pestaña, esto es más eficiente hace que el navegador vaya más rápido cuando se usan muchas pestañas y gasta menos batería.

       Ahora mismo lo soporta sin prefijo Internet Explorer 10, y con prefijos firefox (4+), Chrome (10+), Safari (6+) y IOS (6+). No tiene soporte para Safari 5.1, Opera, Android e IE 9 y anteriores, por lo que solo podemos usar este método para la mitad de clientes y con prefijo. Para solucionar este problema recurro a la solución de paulirish (un poco alterada):

window.animar = (function(){
    return window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function( callback ){
        window.setTimeout(callback, 16);
    };
})();

       Ahora al usar la función animar como usaríamos requestAnimationFrame, se usará request en los navegadores que lo soporten y usaremos setTimeout en los demás.

       Existe en algunos navegadores un efecto extraño al pasar a otra pestaña y volver, si queremos hacer animaciones muy finas podemos reparar este problema con la solución de mozilla hacks.

       Hay que tener en cuenta que la ventaja de no tener que preocuparnos por el tiempo de repintado se puede volver en nuestra contra. Si deseamos saber cuanto tiempo pasa entre repintado para actualizar un reloj, por ejemplo, o simplemente para hacer que la animación tenga una velocidad más o menos constante independientemente del equipo utilizado, tendremos que usar una variable donde apuntamos el tiempo (con Date) e ir comparando... Como esto es habitual y un poco engorroso, se están planteando incluirlo en la propia especificación para que sea más sencillo. A la espera de lo que deciden voy a pasar por encima de este tema, en el enlace de Mozilla Hacks podéis encontrar el código para controlar este tiempo.

       El ejemplo final queda:

<canvas id="canvas1" width="500" height="300"></canvas>
<script>
  var t=0;
  var derecha=true;
  var canvas=document.getElementById("canvas1");
  var ctx = canvas.getContext("2d");
  window.animar = (function(){
      return window.requestAnimationFrame ||
               window.webkitRequestAnimationFrame ||
               window.mozRequestAnimationFrame ||
               function( callback ){
                 window.setTimeout(callback, 16);
              };
  })();
  function pintar(){
      canvas.width=canvas.width;
      if (derecha){
          t+=10;
          if (t>100){
             derecha=false;
          }
      }else{
          t-=10;
          if (t<-100){
             derecha=true;
          }
      }
      ctx.translate(t,0);
      ctx.fillStyle = "rgb(200,200,0)";
      ctx.fillRect(150, 100, 200, 100);
      animar(pintar);
  }
</script>