31

How to animate a vector path like it's being drawn, progressively? In other words, slowly show the path pixel by pixel.

I'm using Raphaël.js, but if your answer is not library specific—like maybe there's some general programming pattern for doing that kind of thing (I'm fairly new to vector animation)—it's welcome!


It's easy to do with straight paths, as easy as an example on that page::

path("M114 253").animate({path: "M114 253 L 234 253"});

But try to change code on that page, say, this way::

path("M114 26").animate({path: "M114 26 C 24 23 234 253 234 253"});

And you'll see what I mean. Path is certainly animated from it initial state (point "M114 26") to the end state (curve "C 24 23 234 253 234 253" starting on point "M114 26"), but not in a way specified in question, not like it's being drawn.

I don't see how animateAlong can do that. It can animate an object along a path, but how can I make this path to gradually show itself while object is being animated along it?


The solution?

(Via peteorpeter's answer.)

Seems like currently the best way to do it is via 'fake' dashes using raw SVG. For the explanation see this demo or this document, page 4.

How produce progressive drawing?

We have to use stroke-dasharray and stroke-dashoffset and know length of curve to draw. This code draw nothing on screen for circle, ellipse, polyline, polygone or path:

<[element] style="stroke-dasharray:[curve_length],[curve_length]; stroke-dashoffset:[curve_length]"/>

If in animate element stroke-dashoffset decrease to 0, we get progressive drawing of curve.

<circle cx="200" cy="200" r="115"
    style="fill:none; stroke:blue; stroke-dasharray:723,723; stroke-dashoffset:723">
    <animate begin="0" attributeName="stroke-dashoffset"
        from="723" to="0" dur="5s" fill="freeze"/>
</circle>

If you know a better way, please leave an answer.


Update (26 Apr. 2012): Found an example that illustrates the idea well, see Animated Bézier Curves.

Community
  • 1
  • 1
Anton Strogonoff
  • 32,294
  • 8
  • 53
  • 61

11 Answers11

27

Maybe someone is searching for an answer, like me for two days now:

// Draw a path and hide it:
var root = paper.path('M0 50L30 50Q100 100 50 50').hide();
var length = root.getTotalLength();

// Setup your animation (in my case jQuery):
element.animate({ 'to': 1 }, {
    duration: 500,
    step: function(pos, fx) {
        var offset = length * fx.pos;
        var subpath = root.getSubpath(0, offset);
        paper.clear();
        paper.path(subpath);
    }
});

That did the trick for me, only by using RaphaelJS methods.

Here is a jsFiddle example as requested in the comments, http://jsfiddle.net/eA8bj/

Tetraodon
  • 128
  • 8
davidenke
  • 436
  • 6
  • 13
  • Yeah—that's actually what I meant by ‘draw a path, make it invisible, break it into a number of subpaths, and show that subpaths one by one’ in my answer, but thanks for explaining with code. =) I didn't like this solution much since it's not exactly smooth, and can be resource-demanding. – Anton Strogonoff Feb 05 '12 at 06:09
  • It's very smooth in my cases. But I haven't tested it that much in different browsers... Combined with some easing equations in jquery, it's very powerfull (for my single paths...). – davidenke Feb 15 '12 at 19:22
  • Nice work! Was looking for a solution, thanks to you it took me only one hour! – Tosh Jul 12 '12 at 23:21
  • It would be nice if someone could put this in jsfiddle because I'm having a hard time getting it to work, admittedly I am new to Raphael. – cwd Oct 25 '12 at 14:11
  • Major +1 for Raphael solution – Chris Wilson Jan 10 '13 at 13:19
  • 2
    I've adapted your jsFiddle a bit so it is isolated to a single raphael path element: [http://jsfiddle.net/eA8bj/63/](http://jsfiddle.net/eA8bj/63/) – bspoel Feb 13 '13 at 12:42
17

Eureka! (Maybe - assuming you're comfortable stepping outside the friendly realm of Raphael into pure SVG land...)

You can use SVG keyTimes and keySplines.

Here's a working example:

http://www.carto.net/svg/samples/animated_bustrack.shtml

...and here's some potentially useful explanation:

http://msdn.microsoft.com/en-us/library/ms533119(v=vs.85).aspx

peteorpeter
  • 4,037
  • 2
  • 29
  • 47
  • Wow, looking at the demos... I guess currently it's the best way! I have troubles understanding all that manipulations with dashes right now, so I'm yet to try it myself. Also, the site from the first link looks like a great resource on SVG. **PS.** The realm of Raphael is not only friendly, but producing graphics that work (with poor perfomance though) on IE6. Which counts sometimes, sadly. – Anton Strogonoff Feb 22 '11 at 04:39
  • 1
    It looks like SVGweb may support this SVG feature, meaning better IE support and performance via flash: http://code.google.com/p/svgweb/source/browse/trunk/src/org/svgweb/nodes/SVGAnimateNode.as?r=1181 – peteorpeter Feb 22 '11 at 12:34
11

I'd like to offer an alternative, Raphael+JS-only solution that I have made substantial use of in my own work. It has several advantages over davidenke's solution:

  1. Doesn't clear the paper with each cycle, allowing the animated path to coexist nicely with other elements;
  2. Reuses a single path with Raphael's own progressive animation, making for smoother animations;
  3. Substantially less resource intensive.

Here's the method (which could quite easily be retooled into an extension):

function drawpath( canvas, pathstr, duration, attr, callback )
{
    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 last_point = guide_path.getPointAtLength( 0 );
    var start_time = new Date().getTime();
    var interval_length = 50;
    var result = path;        

    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 );
        if ( elapsed_time >= duration )
        {
            clearInterval( interval_id );
            if ( callback != undefined ) callback();
                guide_path.remove();
        }                                       
    }, interval_length );  
    return result;
}

And here are two samples of its usage on my site: one for Path Transformation, and the other for Progressive Lettering.

Kevin Nielsen
  • 4,413
  • 21
  • 26
  • Thanks for two demos in tail of answer, it was a great help. – Tommi Nov 26 '13 at 13:41
  • Its a nice solution Kevin. Need a suggestion here, when line is drawn progressively, the line drawn at the end is in butt Cap, even though i had given strokelinecap and strokelinejoin as round. Let me if you have any solution for this. If i try to close the line with z in the last index in the array, then last point of the line is jointed with the start point of the line – PCA Jul 21 '15 at 11:02
7

I've created a script for this: Scribble.js, based on this great dasharray/dashoffset technique.

Just instantiate it overs a bunch of SVG <path>s:

var scribble = new Scribble(paths, {duration: 3000});
scribble.erase();
scribble.draw(function () {
    // done
});

--

NB: Full USAGE code here: https://gist.github.com/abernier/e082a201b0865de1a41f#file-index-html-L31

Enjoy ;)

abernier
  • 27,030
  • 20
  • 83
  • 114
6

Using "pathLength" attribute we can set virtual length to the path. From then we can use this virtual length in "stroke-dasharray". So if we set "pathLength" to 100 units we then can set "stroke-dasharray" to "50,50" wich wuld be exactly 50%, 50% of the path!

There is one problem with this approach: the only browser that supports this attribute is Opera 11.

Here is example of smooth curve drawind animation without javascript or hardcoded length.(Works properly only in Opera 11)

Brazhnyk Yuriy
  • 464
  • 4
  • 10
  • Support for pathLength is now in FF ([as of v6](https://bugzilla.mozilla.org/show_bug.cgi?id=643419)). Chrome not yet. – Doug Miller Oct 15 '11 at 01:20
3

Anton & Peteorpeter's solution sadly breaks down in Chrome when paths get complicated. It's fine for the bus map in that linked demo. Check out this animated "flower petals" jsfiddle I created, which draws correctly in FF10 and Safari5, but flickers uncontrollably in Chrome:

http://jsfiddle.net/VjMvz/

(This is all HTML and inline SVG, no javascript.)

I'm still looking for a non-Flash solution for this. AnimateAlong obviously won't cut it for what I'm doing. Raphael.js could work, though it threatens to turn into callback spaghetti really fast.

Davidenke, can you post a working jsfiddle with your solution? I just can't get it to work. I'm getting an error in Chrome 18 that nodes that are set to "display: none" with your ".hide" have no method 'getTotalLength'.

Ben
  • 11,082
  • 8
  • 33
  • 47
  • Thanks for the demo, can confirm flickering. Seems like this question still unsolved then. (But, while your post is helpful, I should note it's not actually an answer, more like a correction and a question for @davidenke, so maybe you should post it as a comment to his answer.) – Anton Strogonoff Mar 24 '12 at 18:25
  • I was a total newb and couldn't comment at the time. Sorry! Flickering is solved in Webkit now. (I bugged it when I found it.) See: http://trac.webkit.org/changeset/116030 . Anton's solution works just fine now. Paths away! – Ben Jul 15 '12 at 22:46
  • Just a note: there's no flickering on this jsfiddle in Chrome 44.0.2403.157 (64-bit) on my old MacBook. – Dave Everitt Sep 03 '15 at 10:19
2

Unfortunately, as you seem to agree, you probably can't do this elegantly in Raphael.

However, if, by some stroke of %deity% you don't need to support IE for this particular feature, you could forgo the Raphael API and manipulate the SVG directly. Then, perhaps, you could rig a mask to ride along the path and reveal the line at a natural pace.

You could degrade gracefully in IE to simply show the path using Raphael, without animation.

peteorpeter
  • 4,037
  • 2
  • 29
  • 47
  • That seems to be pretty close… Thanks for the suggestion and links. Will read the specs more carefully to find out what else I can do with SVG without Raphael. ⁋ But mask, say I go for it. So it gonna be a big rectangular one covering the whole path initially and then, while going along, revealing what's left after, right? But if the path will turn around (imagine some kind of a labyrinth) then mask will start hiding again anyway and it won't make sense. Or am I misunderstanding something? – Anton Strogonoff Feb 02 '11 at 05:12
  • No, you raise a good point regarding the mask scenario. Putting overlaps aside, even on a simple closed path, say a circle, you would have trouble as the mask covered up what had already been shown... This solution may be dead in the water :( – peteorpeter Feb 02 '11 at 16:51
1

Just an update to this, you could try Lazy Line Painter

JackSD
  • 197
  • 10
  • Looks like it does the thing! I'll explain briefly how it seems to work, although it would be better if you included that in your answer. Anyway: you drop your SVG (have to be without fills and closed paths) to the on-site convertor, and it gives you back some JS code. You include that code into your page's JS, along with jQuery, Raphaёl, and Lazy Line Painter library. After you initialize Lazy Line Painter with that JS code, you can start painting the path by simply calling one jQuery method. – Anton Strogonoff Aug 03 '13 at 09:31
1

i was just doing exactly this. The first thing i tried was Anton's solution but the performance sucks.

In the end the easiest way to get the result i wanted was to use the alternative "keyframe" syntax for the animate function.

draw the final path invisibly, then generate a whole bunch of key frames by using getSubpath in a loop.

create a new path that is visible and equals the first keyframe.

then do something like:

path.anmimate({ keyFrameObject, timeframe });

you shouldn't need a keyframe for every pixel that you want to draw. After playing around with the parameters, i found that a value of 100px per keyframe worked for the complexity/size of what i was trying to "draw"

David Meister
  • 3,941
  • 1
  • 26
  • 27
0

Have you tried Raphael's animateAlong? You can see it in action on a demo page.

user123444555621
  • 148,182
  • 27
  • 114
  • 126
  • I had a thought about `animateAlong`, but can't see how it could do the thing. It can animate an object along a path, but how can I make this path to gradually show itself while an object is being animated along it? – Anton Strogonoff Jan 10 '11 at 01:38
0

Alright, here's my thoughts on this… The solution's too far from ideal.

To gradually show the path mean we should show it, like, dot by dot. And vector paths consist not of dots, but of curves, so it appears to me there's no ‘natural’ way to gradually ‘draw’ the path in vector graphics. (Though I'm fairly new to this and may be mistaken.)

The only way would be to somehow convert a path to a number of dots and show them one by one.

Currently my workaround is to draw a path, make it invisible, break it into a number of subpaths, and show that subpaths one by one.

This isn't hard to do with Raphael, but it's not elegant either, and quite slow on a large paths. Not accepting my answer, hoping there's a better way…

Anton Strogonoff
  • 32,294
  • 8
  • 53
  • 61