SVG animation along path with Raphael

Toby Hede picture Toby Hede · Apr 13, 2010 · Viewed 7.1k times · Source

I have a rather interesting issue with SVG animation.

I am animating along a circular path using Raphael

obj = canvas.circle(x, y, size);
path = canvas.circlePath(x, y, radius);                
path = canvas.path(path); //generate path from path value string
obj.animateAlong(path, rate, false);

The circlePath method is one I have created myself to generate the circle path in SVG path notation:

Raphael.fn.circlePath = function(x , y, r) {      
  var s = "M" + x + "," + (y-r) + "A"+r+","+r+",0,1,1,"+(x-0.1)+","+(y-r)+" z";   
  return s; 
} 

So far, so good - everything works. I have my object (obj) animating along the circular path.

BUT:

The animation only works if I create the object at the same X, Y coords as the path itself.

If I start the animation from any other coordinates (say, half-way along the path) the object animates in a circle of the correct radius, however it starts the animation from the object X,Y coordinates, rather than along the path as it is displayed visually.

Ideally I would like to be able to stop/start the animation - the same problem occurs on restart. When I stop then restart the animation, it animates in a circle starting from the stopped X,Y.

UPDATE

I created a page that demonstrates the issue: http://infinity.heroku.com/star_systems/48eff2552eeec9fe56cb9420a2e0fc9a1d3d73fb/demo

Click "start" to start the animation. When you stop and re-start the animation, it continues from the current circle coords in a circle of the correct dimensions.

Answer

Jared Forsyth picture Jared Forsyth · Apr 18, 2010

The problem is that Raphael has no way of knowing that the circle is already part-way along the path. The "start" function means just that -- start an animation. imo it would be broken if it did anything else.

That said, your use case is a valid one, and might warrant another function -- a 'pause' of some sort. Of course, getting that into trunk would take longer probably than you want to wait.

From the Raphael source code, here's what happens when you call 'stop'.

Element[proto].stop = function () {
    animationElements[this.id] && animationElements[length]--;
    delete animationElements[this.id];
    return this;
};

This decrements the total number of animations, and removes that animation from the list. Here's what the 'pause' function might look like:

Element[proto].pause = function () {
    animationElements[this.id] && animationElements[length]--;
    this._paused_anim = animationElements[this.id];
    delete animationElements[this.id];
    return this;
};

this saves the animation to be resumed later. then

Element[proto].unpause = function () {
    this._paused_anim && (animationElements[this.id]=this._paused_anim);
    ++animationElements[length] == 1 && animation();
    return this;
};

would unpause. Given scoping conditions, these two functions might need to be injected right into the Raphael source code (it's core hacking, I know but sometimes there's no alternative). I would put it right below the "stop" function shown above.

Try that, and tell me how it goes.

====EDIT====

Ok, so it looks like you'll have to modify the "start" attribute of animationElements[this.id]... something like:

this._pause_time = (+new Date) - animationElements[this.id].start;

in the pause, and then

animationElements[this.id].start = (+new Date) - this._pause_time;

on resume.

http://github.com/DmitryBaranovskiy/raphael/blob/master/raphael.js#L3064