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?
You can create animations with HTML5 by combining HTML, CSS, and JavaScript (JS), with which you can build shapes. Also, you can control animations and edit images, video, and audio by means of JS or CSS elements, all of which you then add to a drawing board, which you set up with the <canvas> element.
The HTML5 canvas has the potential to become a staple of the web, enjoying ubiquitous browser and platform support in addition to widespread webpage support, as nearly 90% of websites have ported to HTML5.
The canvas paths allow you to draw custom shapes. In HTML5 canvas, a path consists of a list of zero or more subpaths. There are one or more points in each subpath that are connected by straight lines or curves. To create a custom shape perform the following steps : Use beginPath() method to start drawing the path.
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/
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With