How to animate an image along a path with Bezier curves - javascript

My goal:
Move/animate an image along a path like the drawing below (Could be connecting bezier curves).
Must work in IE7+, don't what to build multiple solutions.
I can pause/resume the moving image.
The image will keep moving along the path (repeat).
What I have considered
CANVAS: not supported in IE7+8, haven't tested explorercanvas yet! Foresee some z-index issues.
SVG, not supported in IE7+8.
jQuery.path, a plugin that extends the jQuery animate function. Can't figure out the resume part and I want to use CSS transforms when supported.
My plan
Animate the image with CSS 3D transform, CSS 2d transform or jQuery.animate (what supported) and requestAnimationFrame.
Calculate all the coordinates and simple move the image pixel by pixel.
My question
Does my plan sound like madness? Better suggestions?
Do you foresee some performance issues? I might end up with 5K or 10K coordinates.
Do you know a smart way, a program, a function or anything similar to calculate all the coordinates?

There's a tiny script (SVG based), just for animation which isn't in straight lines,
called pathAnimator (2kb), It's very very small and very efficient.
No jQuery required.
For example:
var path = "M150 0 L75 200 L225 200 Z"; // an SVG path
pathAnimator = new PathAnimator( path ), // initiate a new pathAnimator object
speed = 6, // seconds that will take going through the whole path
reverse = false, // go back or forward along the path
startOffset = 0, // between 0% to 100%
easing = function(t){ t*(2-t) }; // optional easing function
pathAnimator.start( speed, step, reverse, startOffset, finish, easing);
function step( point, angle ){
// do something every "frame" with: point.x, point.y & angle
}
function finish(){
// do something when animation is done
}
(can even get more efficient using fastdom)

I would recommend you to use GSAP : http://www.greensock.com/get-started-js/
With that you can handle timelines, and here is a bezier plugin : http://api.greensock.com/js/com/greensock/plugins/BezierPlugin.html
regards

Related

How to curl paths into spiral on hover using Raphael JS

I have a set of Raphael paths that I want to animate when a separate path is hovered over.
The client sent an SVG illustration that I had converted into Raphael code. This is just a small section of the larger image.
What I'm trying to do is as follows:
starting canvas
Start with a set of path objects in a line. When you hover over the red path they animate in a spiral until they form
ending canvas
I've done some research, and I think that I'm going to have to animate each circle along a hidden path that depicts the arcs they have to move to get to the final spiral shape (Animate along a path), but I'm not sure if this is the most efficient way to create this animation.
On top of that, I'm not really sure how to calculate the angle and size of the hidden paths the circles would be following. What is going to be the best way to create this animation?
Thanks!
I think moving each circle along a hidden path is easiest way.
I would suggest to create a new SVG file in InkScape vector editor, draw circles in start and end positions and then connect them with arcs. Save the file. Then open the file in any text editor and copy all data to your JS code (copy path's "d" parameter and coordinates of circles).
It's been more than one year since it's asked but maybe it might help other people.
Using simple algebra thought in high school you can define two functions: linear and spiral.
function linear(i){
x = i*15;
y = x;
return {cx:x+offset,cy:y+offset};
}
function spiral(i){
r = 50 - i*5;
x = r*Math.cos(i);
y = r*Math.sin(i);
return {cx:x+offset,cy:y+offset};
}
This is just a 5 min fiddling, so it might seem cheap. Of course, you can define better algorithms using transform functions.
jsfiddle here

How can I make Raphael.js elements "wiggle" on the canvas?

I'm working on a project that uses SVG with Raphael.js. One component is a group of circles, each of which "wiggles" around randomly - that is, slowly moves along the x and y axes a small amount, and in random directions. Think of it like putting a marble on your palm and shaking your palm around slowly.
Is anyone aware of a Raphael.js plugin or code example that already accomplishes something like this? I'm not terribly particular about the effect - it just needs to be subtle/smooth and continuous.
If I need to create something on my own, do you have any suggestions for how I might go about it? My initial idea is along these lines:
Draw a circle on the canvas.
Start a loop that:
Randomly finds x and y coordinates within some circular boundary anchored on the circle's center point.
Animates the circle from its current location to those coordinates over a random time interval, using in/out easing to smooth the effect.
My concern is that this might look too mechanical - i.e., I assume it will look more like the circle is tracing a star pattern, or having a a seizure, or something like that. Ideally it would curve smoothly through the random points that it generates, but that seems far more complex.
If you can recommend any other code (preferably JavaScript) that I could adapt, that would be great too - e.g., a jQuery plugin or the like. I found one named jquery-wiggle, but that seems to only work along one axis.
Thanks in advance for any advice!
Something like the following could do it:
var paper = Raphael('canvas', 300, 300);
var circle_count = 40;
var wbound = 10; // how far an element can wiggle.
var circleholder = paper.set();
function rdm(from, to){
return Math.floor(Math.random() * (to - from + 1) + from);
}
// add a wiggle method to elements
Raphael.el.wiggle = function() {
var newcx = this.attrs.origCx + rdm(-wbound, wbound);
var newcy = this.attrs.origCy + rdm(-wbound, wbound);
this.animate({cx: newcx, cy: newcy}, 500, '<');
}
// draw our circles
// hackish: setting circle.attrs.origCx
for (var i=0;i<circle_count;i++) {
var cx = rdm(0, 280);
var cy = rdm(0, 280);
var rad = rdm(0, 15);
var circle = paper.circle(cx, cy, rad);
circle.attrs.origCx = cx;
circle.attrs.origCy = cy;
circleholder.push(circle);
}
// loop over all circles and wiggle
function wiggleall() {
for (var i=0;i<circleholder.length;i++) {
circleholder[i].wiggle();
}
}
// call wiggleAll every second
setInterval(function() {wiggleall()}, 1000);
http://jsfiddle.net/UDWW6/1/
Changing the easing, and delays between certain things happening should at least help in making things look a little more natural. Hope that helps.
You can accomplish a similar effect by extending Raphael's default easing formulas:
Raphael.easing_formulas["wiggle"] = function(n) { return Math.random() * 5 };
[shape].animate({transform:"T1,1"}, 500, "wiggle", function(e) {
this.transform("T0,0");
});
Easing functions take a ratio of time elapsed to total time and manipulate it. The returned value is applied to the properties being animated.
This easing function ignores n and returns a random value. You can create any wiggle you like by playing with the return formula.
A callback function is necessary if you want the shape to end up back where it began, since applying a transformation that does not move the shape does not produce an animation. You'll probably have to alter the transformation values.
Hope this is useful!
There is a very good set of easing effects available in Raphael.
Here's a random set of circles that are "given" bounce easing.
Dynamically add animation to objects
The full range of easing effects can be found here. You can play around with them and reference the latest documentation at the same time.
Putting calls in a loop is not the thing to do, though. Use callbacks, which are readily available.

Raphael 2.0 - how to correctly set the rotation point

I don't understand how to rotate my object at a certain point. This is the javascript I have
// create needle
var rsr = Raphael('rsr', '320', '240');
var needle = rsr.path("m 156.74443,870.84631 -2.26177,119.38851
4.38851,0 z"); needle.attr({id: 'needle',parent: 'layer1',fill:
'#ff6600',stroke: '#000000',"stroke-width": '0.61',"stroke-linecap":
'butt',"stroke-linejoin": 'miter',"stroke-miterlimit": '4',"stroke-
opacity": '1',"stroke-dasharray": 'none'});
needle.rotate(0);
needle.transform("t0,-812.36218").data('id', 'needle');
// get needle bounding box
var needleBox = needle.getBBox();
// calculate rotation point (bottom middle)
var x_rotate_point = needleBox.x + (needleBox.width/2);
var y_rotate_point = needleBox.y + needleBox.height;
// rotate needle
needle.attr({rotation: 0}).animate({transform:"r45,"+x_rotate_point
+","+y_rotate_point}, 6000);
// Creates circle at rotation point
var circle = rsr.circle(x_rotate_point, y_rotate_point, 10);
circle.attr("fill", "#f00");
circle.attr("stroke", "#fff");
I have created a dummy circle to check if my coordinates of my center point is correct
and it is, but it doesn't rotate around that point :-/
When I created gauges in different frameworks that always seemed the way to go, but that logic doesn't seem to translate well into Raphael 2.0.
I did google for it and found some entries but the problem seems it is for
older versions which doesn't translate well because a lot of
stuff got changed or is deprecated.
You are setting the center of rotation correctly, but there are some other things going on which are causing some confusion. I was able to get this to work by changing the animation target to:
{transform: needle.attr("transform") + "R45,"+x_rotate_point+","+y_rotate_point}
I think the way you had it, the animation was gradually removing the previous translation while also doing the rotation. This addition allows it to accumulate the transformations. Also, note that I had to switch the 'r' to 'R'. It is not really clear to me what the small 'r' is doing in this example.
Anyway, here is a working demo.
Btw, I also commented out a few rotations that didn't seem to be doing anything.

How to draw a vector path progressively? (Raphael.js)

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.
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/
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
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:
Doesn't clear the paper with each cycle, allowing the animated path to coexist nicely with other elements;
Reuses a single path with Raphael's own progressive animation, making for smoother animations;
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.
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 ;)
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)
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'.
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.
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"
Just an update to this, you could try Lazy Line Painter
Have you tried Raphael's animateAlong? You can see it in action on a demo page.
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…

Pixel Perfect Collision Detection in HTML5 Canvas

I want to check a collision between two Sprites in HTML5 canvas. So for the sake of the discussion, let's assume that both sprites are IMG objects and a collision means that the alpha channel is not 0. Now both of these sprites can have a rotation around the object's center but no other transformation in case this makes this any easier.
Now the obvious solution I came up with would be this:
calculate the transformation matrix for both
figure out a rough estimation of the area where the code should test (like offset of both + calculated extra space for the rotation)
for all the pixels in the intersecting rectangle, transform the coordinate and test the image at the calculated position (rounded to nearest neighbor) for the alpha channel. Then abort on first hit.
The problem I see with that is that a) there are no matrix classes in JavaScript which means I have to do that in JavaScript which could be quite slow, I have to test for collisions every frame which makes this pretty expensive. Furthermore I have to replicate something I already have to do on drawing (or what canvas does for me, setting up the matrices).
I wonder if I'm missing anything here and if there is an easier solution for collision detection.
I'm not a javascript coder but I'd imagine the same optimisation tricks work just as well for Javascript as they do for C++.
Just rotate the corners of the sprite instead of every pixel. Effectively you would be doing something like software texture mapping. You could work out the x,y position of a given pixel using various gradient information. Look up software texture mapping for more info.
If you quadtree decomposed the sprite into "hit" and "non-hit" areas then you could effectively check to see if a given quad tree decomposition is all "non-hit", "all hit" or "possible hit" (ie contains hits and non-hit pixels. The first 2 are trivial to pass through. In the last case you then go down to the next decomposition level and repeat the test. This way you only check the pixels you need too and for large areas of "non-hit" and "hit" you don't have to do such a complex set of checks.
Anyway thats just a couple of thoughts.
I have to replicate something I already have to do on drawing
Well, you could make a new rendering context, plot one rotated white-background mask to it, set the compositing operation to lighter and plot the other rotated mask on top at the given offset.
Now if there's a non-white pixel left, there's a hit. You'd still have to getImageData and sift through the pixels to find that out. You might be able to reduce that workload a bit by scaling the resultant image downwards (relying on anti-aliasing to keep some pixels non-white), but I'm thinking it's probably still going to be quite slow.
I have to test for collisions every frame which makes this pretty expensive.
Yeah, I think realistically you're going to be using precalculated collision tables. If you've got space for it, you could store one hit/no hit bit for every combination of sprite a, sprite b, relative rotation, relative-x-normalised-to-rotation and relative-y-normalised-to-rotation. Depending on how many sprites you have and how many steps of rotation or movement, this could get rather large.
A compromise would be to store the pre-rotated masks of each sprite in a JavaScript array (of Number, giving you 32 bits/pixels of easily &&-able data, or as a character in a Sring, giving you 16 bits) and && each line of intersecting sprite masks together.
Or, give up on pixels and start looking at eg. paths.
Same problem, an alternative solution. First I use getImageData data to find a polygon that surrounds the sprite. Careful here because the implementation works with images with transparent background that have a single solid object. Like a ship. The next step is Ramer Douglas Peucker Algorithm to reduce the number of vertices in the polygon. I finally get a polygon of very few vertices easy and cheap to rotate and check collisions with the other polygons for each sprite.
http://jsfiddle.net/rnrlabs/9dxSg/
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var img = document.getElementById("img");
context.drawImage(img, 0,0);
var dat = context.getImageData(0,0,img.width, img.height);
// see jsfiddle
var startPixel = findStartPixel(dat, 0);
var path = followPath(startPixel, dat, 0);
// 4 is RDP epsilon
map1 = properRDP(path.map, 4, path.startpixel.x, path.startpixel.y);
// draw
context.beginPath();
context.moveTo(path.startpixel.x, path.startpixel.x);
for(var i = 0; i < map.length; i++) {
var p = map[i];
context.lineTo(p.x, p.y);
}
context.strokeStyle = 'red';
context.closePath();
context.stroke();

Categories