2

Using RaphaelJS, I adapted a couple scripts to create two animations I want to combine:

  1. First, drawing dashed lines to coordinates http://jsfiddle.net/jbirthler/CvhKx/2/

    var canvas = Raphael('canvas_container', 322, 273);
    var set = canvas.set(canvas.circle(110, 265, 7), canvas.circle(110, 7, 7), canvas.circle(7, 151, 7)).attr({
        stroke: "none",
        fill: "#666" });
    
    var pathstr = "M 109 255 l 0 -245 l -103 141 l 265 0";
    var path = dashline(canvas, pathstr, 4000, {
        stroke: '#828282',
        'stroke-dasharray': "--",
        'stroke-linecap': "butt",
        'stroke-width': 1,
        'fill-opacity': 0 }, 1000);
    
    function dashline(canvas, pathstr, duration, attr) {
    
        var guide_path = canvas.path(pathstr).attr({
            stroke: "none",
            fill: "none"
        });
        var path = canvas.path(guide_path.getSubpath(0, 1)).attr(attr);
        var total_length = guide_path.getTotalLength(guide_path);
        var start_time = new Date().getTime();
        var interval_length = 20;
    
        var interval_id = setInterval(function() {
            var elapsed_time = new Date().getTime() - start_time;
            var this_length = elapsed_time / duration * total_length;
            var subpathstr = guide_path.getSubpath(0, this_length);
            attr.path = subpathstr;
            path.animate(attr, interval_length);
        }, interval_length);
        return path;
    }​;​
    

    And, easing on the path and animating circles when reaching coordinates http://jsfiddle.net/jbirthler/KqjHh/1/

    var canvas = Raphael("holder", 322, 273);
    var set = canvas.set(canvas.circle(110, 265, 7),canvas.circle(110, 7, 7), canvas.circle(7, 151, 7)).attr({stroke:"none", fill: "#666"});
    var c = canvas.circle(110, 265, 10).attr({stroke: "#ddd", "stroke-width": 4});
    var fade = function (id) {
        return function () {
            set[id].attr({fill: "#fff", r: 12}).animate({fill: "#77bf00", r: 8}, 500);
        };
    };
    
    var run = animateCirc();
    
    function animateCirc() {
        var easex = ">",
            easey = ">";
        c.stop().animate({
            "0%":  {cy: 265, easing: easey, callback: fade(0)},
            "40%": {cy: 7, easing: easey, callback: fade(1)},
            "60%": {cy: 151, easing: easey, callback: fade(2)},
            "100%": {cy: 151, easing: easey, callback: fade(3)}
        }, 3000).animate({
            "0%":  {cx: 110, easing: easex},
            "40%": {cx: 110, easing: easex},
            "60%": {cx: 7, easing: easex},
            "100%": {cx: 300, easing: easex}
        }, 3000);
        return run;                
    };​
    

I would like to have the circles animate as the dashed path arrives at their coordinates. If I could get the dashed path to use easing, that'd be a plus but mostly, I'm just looking to combine the two into one.

I'm able to read javascript better than I'm able to write my own scripts but if anyone has any insight on how to break down the dashed line script and the steps the code is taking, that would be hugely beneficial for me.

My first post on stack overflow (yeesh, about time) hope I was specific enough!

jbirthler
  • 23
  • 1
  • 5
  • I've never used Raphael, but is there an animate method with a callback? jQuery's animate function has a callback which means you can apply the animation for each tick, keeping everything perfectly in sync. See my answer here for an example: http://stackoverflow.com/a/13501577/362536 Perhaps there is a similar method for your situation? – Brad Nov 24 '12 at 06:56

2 Answers2

2

I've never used Raphael myself, but here's what I found as your solution:

Your first animation runs within 4 (4000 milliseconds) seconds, which you can see in this block:

var path = dashline(canvas, pathstr, 4000, {
    stroke: '#828282',
    'stroke-dasharray': "--",
    'stroke-linecap': "butt",
    'stroke-width': 1,
    'fill-opacity': 0
}, 1000);

Your next step is to identify your block rendering the circles, here you give it 3 seconds to run in, which can be resolved by changing the last parameter to 4000. Next, you'll notice the percentages. These should involve a conversion calculation to translate the milliseconds (4000) into percentages for each animation point.

I eyeballed the animation points, but the ending code looks something like this:

function animateCirc() {
    var easex = ">",
        easey = ">";
    c.stop().animate({
        "0%":  {cy: 265, easing: easey, callback: fade(0)},
        "35%": {cy: 7, easing: easey, callback: fade(1)},
        "60%": {cy: 151, easing: easey, callback: fade(2)},
        "100%": {cy: 151, easing: easey, callback: fade(3)}
    }, 4000).animate({
        "0%":  {cx: 110, easing: easex},
        "35%": {cx: 110, easing: easex},
        "60%": {cx: 7, easing: easex},
        "100%": {cx: 300, easing: easex}
    }, 4000);
    return run;                
};

You can see the updated (but not 100% synchronized) version here.

var canvas = Raphael('canvas_container', 322, 273);
var set = canvas.set(canvas.circle(110, 265, 7), canvas.circle(110, 7, 7), canvas.circle(7, 151, 7)).attr({
    stroke: "none",
    fill: "#666"
});

var c = canvas.circle(110, 265, 10).attr({stroke: "#999", "stroke-width": 0});
var fade = function (id) {
    return function () {
        set[id].attr({fill: "#fff", r: 12}).animate({fill: "#77bf00", r: 8}, 500);
    };
};

var pathstr = "M 109 255 l 0 -245 l -103 141 l 265 0";
var path = dashline(canvas, pathstr, 4000, {
    stroke: '#828282',
    'stroke-dasharray': "--",
    'stroke-linecap': "butt",
    'stroke-width': 1,
    'fill-opacity': 0
}, 1000);

function dashline(canvas, pathstr, duration, attr) {
    var guide_path = canvas.path(pathstr).attr({
        stroke: "none",
        fill: "none"
    });
    var path = canvas.path(guide_path.getSubpath(0, 1)).attr(attr);
    var total_length = guide_path.getTotalLength(guide_path);
    var start_time = new Date().getTime();
    var interval_length = 20;

    var interval_id = setInterval(function() {
        var elapsed_time = new Date().getTime() - start_time;
        var this_length = elapsed_time / duration * total_length;
        var subpathstr = guide_path.getSubpath(0, this_length);
        attr.path = subpathstr;
        path.animate(attr, interval_length);

    }, interval_length);
    return path;
}
var run = animateCirc();

function animateCirc() {
    var easex = ">",
        easey = ">";
    c.stop().animate({
        "0%":  {cy: 265, easing: easey, callback: fade(0)},
        "35%": {cy: 7, easing: easey, callback: fade(1)},
        "60%": {cy: 151, easing: easey, callback: fade(2)},
        "100%": {cy: 151, easing: easey, callback: fade(3)}
    }, 4000).animate({
        "0%":  {cx: 110, easing: easex},
        "35%": {cx: 110, easing: easex},
        "60%": {cx: 7, easing: easex},
        "100%": {cx: 300, easing: easex}
    }, 4000);
    return run;                
};

​Note that you could really use Raphael, Easel, Kinetic, or any type of Canvas/SVG rendering tool.

Hope this helps!

akjoshi
  • 15,374
  • 13
  • 103
  • 121
adamRenny
  • 38
  • 6
1

@adamRenny's answer is succint, very simple modification (he beat me to it, i'm still writing my answer when he submitted his). But it it seems it doesn't involve real queueing. The timings must be calculated manually to fully synchronize the animations. However, my answer below will drastically change the code. Might or might not be desired.

First thing to do is to split the dashed path into lines (in your case into 3 separate line segments) and animate them in queue. It might be possible to queue the single (combined) path but i haven't tried it.

To ease the process, i extracted all the path coordinates as they are also used by the circles. This way we can draw all our elements in a loop.

var canvas = Raphael('canvas_container', 322, 273),
    // here are the coordinates
    points = [ [110,265], [110,7], [7,151], [300,151] ],
    mCircle = canvas.circle(points[0][0],points[0][1],10).attr({stroke: "#999", "stroke-width": 4}),
    path = [],
    circles = [];

// draw the dots and (starting point of) lines
// note the lines are of 0 length so it's invisible, we only mark its starting point
for (var i = 0; i < points.length - 1; i++) {
    circles[i] = canvas.circle(points[i][0],points[i][1],7).attr({stroke: "none", fill: "#666"});
    path[i] = canvas.path('M'+points[i][0]+' '+points[i][1]).attr({
        stroke: '#828282',
        'stroke-dasharray': "--",
        'stroke-linecap': "butt",
        'stroke-width': 1,
        'fill-opacity': 0
    });

Note that the loop count is points.length - 1 because the last coordinate is only used by the moving circle, we don't draw anything there at this point.

I then create a factory function to generate each set of animation

// function to generate each set of animation
var fn = function(index) {
    var cPath = path[index], cCircle = circles[index],
        x1 = points[index][0], 
        x2 = points[index+1][0], 
        y1 = points[index][1], 
        y2 = points[index+1][1];

    return function(cb) {
        cPath.animate({path:'M'+x1+' '+y1+' L'+x2+' '+y2},500,'easeOut');
        mCircle.animate({cx:x2,cy:y2},500,'easeOut');
        cCircle.attr({fill: "#fff", r: 12}).animate({fill: "#77bf00", r: 8}, 500, cb);
    };
};

 

Here come the hardest part. Managing the animation queue. You can actually use other library like jQuery Deferred. However after hours of despair, i decided to write my own (very simple) queue system.

// my custom Queue class
var Queue = function() {
    this.actionCount = 0;
    this.actions = [];

    var self = this;
    self._cb = function() {
        if (++self.actionCount != self.actions.length) self.run();
    };
};
Queue.prototype.run = function() {
    this.actions[this.actionCount](this._cb);
};
Queue.prototype.add = function(fn) {
    this.actions.push(fn);
};

In the loop, we can then register each set of generated animation into queue. And run the queue after the loop finished

for (var i = 0; i < points.length - 1; i++) {
    circles[i] = canvas.circle(points[i][0],points[i][1],7).attr({stroke: "none", fill: "#666"});
    path[i] = canvas.path(/*...*/);

    queue.add(fn(i));
}
queue.run();

Here is the jsFiddle

 

Things to note

  • The dashed path animation are eased :p
  • Adding more point to be animated is as simple as adding them into points array, the rest is done automatically - jsFiddle
  • You should be able to tweak the animation from the fn function
  • The queue class is very simple without any checking and untested for production. I created it for the sole purpose of answering this thread. Standard precaution applies
  • This is my first time with Raphael, so correction are welcome
  • The loop does not pass JSLint because i call a factory function that create another function. This seems like a bad thing to do in loop
  • As i create the jsFiddle before answering, the code here is better commented than jsFiddle
Community
  • 1
  • 1
BlackFur
  • 328
  • 2
  • 14