Easing circular

facebook-svg gplus-svg twitter-svg

Imaginemos una pelota que sigue al ratón, no en línea recta sino en un movimiento circular. Esto es fácil. Primero iniciamos el canvas y declaramos algunas variables necesarias:


var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var cw = canvas.width = 300,
  cx = cw / 2;
var ch = canvas.height = 300,
  cy = ch / 2;

ctx.fillStyle = "#6ab150";
ctx.strokeStyle = "#ccc";
var rad = (Math.PI / 180);// un grado sexagesimal en radianes 
var R = 100; // el radio de la trayectoria circular
var centro = { // el centro de la trayectoria circular
  x: cx,
  y: cy
};
var m = {// inicia el ratón
  x: cx,
  y: cy,
  a: 0
}; 

La función dibujarTrayectoria() hace exactamente esto dibuja la trayectoria a seguir por la pelota.


function dibujarTrayectoria() {
  ctx.beginPath();
  ctx.arc(cx, cy, R, 0, 2 * Math.PI);
  ctx.stroke();
}

La función Pelota() es un constructor que crea una nueva pelota.

El nuevo objeto tiene dos métodos: un método para dibujar(), y otro método para actualizar() la pelota en función de la posición del ratón ( m ).

También necesitamos una manera de detectar la posición del ratón:


function oMousePos(canvas, evt) {
  //detecta la posición del ratón y devuelve un objeto con las coordenadas de este
  var ClientRect = canvas.getBoundingClientRect();
  return {
    x: Math.round(evt.clientX - ClientRect.left),
    y: Math.round(evt.clientY - ClientRect.top)
  }
}

A continuación instanciamos un nueva pelota, dibujamos la trayectoria y dibujamos la pelota.


var pelota = new Pelota();
dibujarTrayectoria();
pelota.dibujar();

Aquí empieza la parte realmente interesante: un evento mousemove detecta la posición del ratón ( m ) cuando este pasa por encima del canvas y calcula el ángulo correspondiente a la posición del ratón ( m.a ) relativo al centro de la trayectoria ( centro ).


// detecta la posición del ratón, cuando el ratón pasa por encima del canvas (on "mousemove",):
canvas.addEventListener("mousemove", function(evt) {
  m = oMousePos(canvas, evt);
  var dx = (m.x - centro.x);
  var dy = (m.y - centro.y);
  m.a = Math.atan2(dy, dx);
  console.log(m.a);
}, false);

Finalmente la función Animacion() ( una función recursiva que se llama a si misma ) limpia el canvas con cleatRect() ( borra última fotograma ), actualiza las coordenadas de la pelota, y dibuja de nuevo la trayectoria y la pelota.


function Animacion() {
  requestId = window.requestAnimationFrame(Animacion);
  ctx.clearRect(0, 0, cw, ch);
  pelota.actualizar();
  dibujarTrayectoria()
  pelota.dibujar();
}
requestId = window.requestAnimationFrame(Animacion);

Veamos el código

See the Pen easing circular ES #0 by Gabi (@enxaneta) on CodePen.

Easing circular

Para utilizar el easing ( var easing = .1; ) en este caso calculamos la velocidad angular de esta manera:

1. Primero calculamos el ángulo ( m.a ) correspondiente a la posición del ratón ( m ), relativo al centro de la trayectoria ( c )

var dx = (m.x - c.x);
var dy = (m.y - c.y);
m.a = Math.atan2(dy, dx);

2. Calculamos la velocidad del objeto ( o ) que queremos mover, la pelota en este caso.

o.va = (m.a - o.a)*easing;

3. Actualizamos las coordenadas del objeto o ( recuerde: en este caso el objeto es la pelota ):

o.x = cx + R*Math.cos(o.a);
o.y = cy + R*Math.sin(o.a);

En cada fotograma volvemos a repetir todo esto, así que podemos ponerlo en una función:


function Interpolar(o,m,c,easing){
      // m es el ratón o sea el punto de destino:  m = {x:cx,y:cy,a:0};
      // o es el objeto que queremos mover, la pelota en este caso  o = {x:cx,y:cy,a:0};
      // c es el centro de la trayectoria  c = {x:cx,y:cy};
      var dx = (m.x - c.x);
      var dy = (m.y - c.y);
      m.a = Math.atan2(dy, dx);
      
      o.va = (m.a - o.a)*easing;
      
      o.a += o.va;
      o.x = cx + R*Math.cos(o.a);
      o.y = cy + R*Math.sin(o.a);
    }

También hay que modificar la función Animación():

function Animacion() {
  requestId = window.requestAnimationFrame(Animacion);
  fotogramas ++;
  ctx.clearRect(0,0,cw,ch);
  Interpolar(pelota,m,centro,easing);
  dibujarTrayectoria ()
  pelota.dibujar();

}

Veamos el código:

See the Pen easing circular ES #1 by Gabi (@enxaneta) on CodePen.

Es obvio que ¡¡HAY UN PROBLEMA, Y GORDO!! Cuando el ratón llega a 180° ( Math.PI ) la pelota da la vuelta a toda la trayectoria para llegar a lo que nosotros consideramos 181°. Esto pasa porque para Math.atan2 "el siguiente punto" no es 181° sino  -179°.

Lea este articulo acerca de la arcotangente en JavaScript. Math.atan versus Math.atan2.

Para arreglar este problema sencillamente añadimos dos líneas de código a la función Interpolar().

if ( o.a < m.a - Math.PI ) {o.a = o.a + 2*Math.PI;}
if ( o.a > m.a + Math.PI ) {o.a = o.a - 2*Math.PI;}

function Interpolar(o,m,c,easing){
      // m es el ratón o sea el punto de destino:  m = {x:cx,y:cy,a:0};
      // o es el objeto que queremos mover, la pelota en este caso  o = {x:cx,y:cy,a:0};
      // c es el centro de la trayectoria  c = {x:cx,y:cy};
      var dx = (m.x - c.x);
      var dy = (m.y - c.y);
      m.a = Math.atan2(dy, dx);
    
      // añadimos estas dos líneas de código
      if ( o.a < m.a - Math.PI ) {o.a = o.a + 2*Math.PI;}
      if ( o.a > m.a + Math.PI ) {o.a = o.a - 2*Math.PI;}
      
      o.va = (m.a - o.a)*easing;
      
      o.a += o.va;
      o.x = cx + R*Math.cos(o.a);
      o.y = cy + R*Math.sin(o.a);
}

Ahora sí. Veamos de nuevo el código:

See the Pen easing circular ES #2 by Gabi (@enxaneta) on CodePen.