18

I'm a bit new to canvas and such so forgive if it's a trivial question.

I'd like to be able to animate an object following a path (defined as bezier path) but I'm not sure how to do it.

I've looked at Raphael but I can't work out how to follow the path over time.

Cake JS looked promising in the demo, but I'm really struggling the documentation, or lack thereof in this case.

Has anyone got some working example of this?

Kara
  • 6,115
  • 16
  • 50
  • 57
Ben
  • 20,737
  • 12
  • 71
  • 115

3 Answers3

21

Use the code on my website from this related question, but instead of changing the .style.left and such in the callback, erase and re-draw your canvas with the item at the new location (and optionally rotation).

Note that this uses SVG internally to easily interpolate points along a bézier curve, but you can use the points it gives you for whatever you want (including drawing on a Canvas).

In case my site is down, here's a current snapshot of the library:

function CurveAnimator(from,to,c1,c2){
  this.path = document.createElementNS('http://www.w3.org/2000/svg','path');
  if (!c1) c1 = from;
  if (!c2) c2 = to;
  this.path.setAttribute('d','M'+from.join(',')+'C'+c1.join(',')+' '+c2.join(',')+' '+to.join(','));
  this.updatePath();
  CurveAnimator.lastCreated = this;
}
CurveAnimator.prototype.animate = function(duration,callback,delay){
  var curveAnim = this;
  // TODO: Use requestAnimationFrame if a delay isn't passed
  if (!delay) delay = 1/40;
  clearInterval(curveAnim.animTimer);
  var startTime = new Date;
  curveAnim.animTimer = setInterval(function(){
    var now = new Date;
    var elapsed = (now-startTime)/1000;
    var percent = elapsed/duration;
    if (percent>=1){
      percent = 1;
      clearInterval(curveAnim.animTimer);
    }
    var p1 = curveAnim.pointAt(percent-0.01),
        p2 = curveAnim.pointAt(percent+0.01);
    callback(curveAnim.pointAt(percent),Math.atan2(p2.y-p1.y,p2.x-p1.x)*180/Math.PI);
  },delay*1000);
};
CurveAnimator.prototype.stop = function(){
  clearInterval(this.animTimer);
};
CurveAnimator.prototype.pointAt = function(percent){
  return this.path.getPointAtLength(this.len*percent);
};
CurveAnimator.prototype.updatePath = function(){
  this.len = this.path.getTotalLength();
};
CurveAnimator.prototype.setStart = function(x,y){
  var M = this.path.pathSegList.getItem(0);
  M.x = x; M.y = y;
  this.updatePath();
  return this;
};
CurveAnimator.prototype.setEnd = function(x,y){
  var C = this.path.pathSegList.getItem(1);
  C.x = x; C.y = y;
  this.updatePath();
  return this;
};
CurveAnimator.prototype.setStartDirection = function(x,y){
  var C = this.path.pathSegList.getItem(1);
  C.x1 = x; C.y1 = y;
  this.updatePath();
  return this;
};
CurveAnimator.prototype.setEndDirection = function(x,y){
  var C = this.path.pathSegList.getItem(1);
  C.x2 = x; C.y2 = y;
  this.updatePath();
  return this;
};

…and here's how you might use it:

var ctx = document.querySelector('canvas').getContext('2d');
ctx.fillStyle = 'red';

var curve = new CurveAnimator([50, 300], [350, 300], [445, 39], [1, 106]);

curve.animate(5, function(point, angle) {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    ctx.fillRect(point.x-10, point.y-10, 20, 20);
});​

In action: http://jsfiddle.net/Z2YSt/

Community
  • 1
  • 1
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • that actually works with any svg path... just need to change this.path.setAttribute to init with a given path.. – Ben Feb 14 '12 at 04:54
  • @Ben Of course it does. :) You might be interested in [this related page of mine](http://phrogz.net/svg/convert_path_to_polygon.xhtml). – Phrogz Feb 14 '12 at 04:56
  • @Phrogz. Gavin, tried to email you regarding your code license at ! [at] phrogz.net but it keeps bouncing back with unauthorized relay. is there another address you can be contacted on? – Ben Feb 20 '12 at 03:23
  • @Ben myfirstname at samedomain – Phrogz Feb 20 '12 at 03:54
  • I don't know too much about Math neither; but you can Google and realize that this solution is ridiculous for such a simple problem especially if you are using canvas. Check the math in my answer below. – Ivan Castellanos Feb 15 '13 at 08:38
8

So, here is the verbose version:

t being any number between 0 and 1 representing time; the p0, p1, p2, p3 objects are the start point, the 1st control point, the 2nd control point an the end point respectively:

var at = 1 - t;
var green1x = p0.x * t + p1.x * at;
var green1y = p0.y * t + p1.y * at;
var green2x = p1.x * t + p2.x * at;
var green2y = p1.y * t + p2.y * at;
var green3x = p2.x * t + p3.x * at;
var green3y = p2.y * t + p3.y * at;
var blue1x = green1x * t + green2x * at;
var blue1y = green1y * t + green2y * at;
var blue2x = green2x * t + green3x * at;
var blue2y = green2y * t + green3y * at;
var finalx = blue1x * t + blue2x * at;
var finaly = blue1y * t + blue2y * at;

Here is a ball using <canvas> following a path in JSfiddle

The names of the variables come from this gif wich is the best explication for bezier curves: http://en.wikipedia.org/wiki/File:Bezier_3_big.gif

A short version of the code, inside a function ready to copy/paste:

var calcBezierPoint = function (t, p0, p1, p2, p3) {
    var data = [p0, p1, p2, p3];
    var at = 1 - t;
    for (var i = 1; i < data.length; i++) {
        for (var k = 0; k < data.length - i; k++) {
            data[k] = {
                x: data[k].x * at + data[k + 1].x * t,
                y: data[k].y * at + data[k + 1].y * t
            };
        }
    }
    return data[0];
};



Related stuff:

Ivan Castellanos
  • 8,041
  • 1
  • 47
  • 42
  • 1
    Thanks for this late contribution. But no need to be so arrogant. @phrogz's answer is pretty awesome, and works on any path. – Ben Feb 15 '13 at 06:05
  • Is very far away from awesome; it uses a proprietary library that it haves too much code by a single individual and it uses SVG that is way slower and is not supported on Android and other devices. ---- And this also works in any path; just change the values of p0, p1, p2 and p3. You can use the first link in "related stuff" if you need help getting the right numbers. – Ivan Castellanos Feb 15 '13 at 08:22
  • We need to help for free and we need to be humble doing it? Tell me all we need to be. – Ivan Castellanos Feb 15 '13 at 17:43
  • Thank you for this very good solution and your examples! It helped me a lot – Maximilian Lindsey Nov 07 '13 at 21:35
  • If you change the first sentence to "Here is an alternative answer," I'll give your solution a +1. – martin jakubik Dec 11 '13 at 14:53
  • The function is concise, and the gif from wikipedia is super helpful! – forresto Mar 25 '14 at 09:43
1

I wouldn't use Canvas for this unless you really have to. SVG has animation along a path built in. Canvas requires quite a bit of math to get it working.

Here's one example of SVG animating along a path.

Here's some discussion about it for raphael: SVG animation along path with Raphael

Please note that Raphael uses SVG and not HTML5 Canvas.


One way to animate along a bezier path in Canvas is to continuously bisect the bezier curve, recoring the midpoints until you have a lot of points (say, 50 points per curve) that you can animate the object along that list of points. Search for bisecting beziers and similar queries for the related math on that.

Community
  • 1
  • 1
Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
  • Thank for your input.. Most of the svg animation examples I've found look ... clunky – Ben Feb 14 '12 at 04:17