Canvas Navigating along a Path

Canvas Navigating along a Path Find point on the curve
This example finds a point on a bezier or cubic curve at the position where the position is the unit distance on the curve 0 <= position <= 1. The position is clamped to the range thus if values < 0 or > 1 are passed they will be set 0,1 respectively.
Pass the function 6 coordinates for quadratic bezier or 8 for cubic. The last optional argument is the returned vector (point). If not given it will be created.
example:
var p1 = {x : 10 , y : 100};
var p2 = {x : 100, y : 200};
var p3 = {x : 200, y : 0};
var p4 = {x : 300, y : 100};
var point = {x : null, y : null};
// for cubic beziers
point = getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, point);
// or No need to set point as it is a referance and will be set
getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, point);
// or to create a new point
var point1 = getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y);
// for quadratic beziers
point = getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, null, null, point);
// or No need to set point as it is a referance and will be set
getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, null, null, point);
// or to create a new point
var point1 = getPointOnCurve(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
The function
getPointOnCurve = function(position, x1, y1, x2, y2, x3, y3, [x4, y4], [vec])
Note: Arguments inside [x4, y4] are optional.
Note: x4,y4 if null, or undefined means that the curve is a quadratic bezier. vec is optional and will hold the returned point if supplied. If not it will be created.
var getPointOnCurve = function(position, x1, y1, x2, y2, x3, y3, x4, y4, vec) {
var vec, quad;
quad = false;
if (vec === undefined) {
vec = {};
}
if (x4 === undefined || x4 === null) {
quad = true;
x4 = x3;
y4 = y3;
}
if (position <= 0) {
vec.x = x1;
vec.y = y1;
return vec;
}
if (position >= 1) {
vec.x = x4;
vec.y = y4;
return vec;
}
c = position;
if (quad) {
x1 += (x2 - x1) * c;
y1 += (y2 - y1) * c;
x2 += (x3 - x2) * c;
y2 += (y3 - y2) * c;
vec.x = x1 + (x2 - x1) * c;
vec.y = y1 + (y2 - y1) * c;
return vec;
}
x1 += (x2 - x1) * c;
y1 += (y2 - y1) * c;
x2 += (x3 - x2) * c;
y2 += (y3 - y2) * c;
x3 += (x4 - x3) * c;
y3 += (y4 - y3) * c;
x1 += (x2 - x1) * c;
y1 += (y2 - y1) * c;
x2 += (x3 - x2) * c;
y2 += (y3 - y2) * c;
vec.x = x1 + (x2 - x1) * c;
vec.y = y1 + (y2 - y1) * c;
return vec;
}
Finding extent of Quadratic Curve
When you need to find the bounding rectangle of a quadratic bezier curve you can use the following performant method.
// This method was discovered by Blindman67 and solves by first normalising the control point thereby
reducing the algorithm complexity
// x1,y1, x2,y2, x3,y3 Start, Control, and End coords of bezier
// [extent] is optional and if provided the extent will be added to it allowing you to use the
function
// to get the extent of many beziers.
// returns extent object (if not supplied a new extent is created)
// Extent object properties
// top, left,right,bottom,width,height
function getQuadraticCurevExtent(x1, y1, x2, y2, x3, y3, extent) {
var brx, bx, x, bry, by, y, px, py;
// solve quadratic for bounds by BM67 normalizing equation
brx = x3 - x1; // get x range
bx = x2 - x1; // get x control point offset
x = bx / brx; // normalise control point which is used to check if maxima is in range
// do the same for the y pointsbry = y3 - y1;
by = y2 - y1;
y = by / bry;
px = x1; // set defaults in case maximas outside range
py = y1;
// find top/left, top/right, bottom/left, or bottom/right
if (x < 0 || x > 1) { // check if x maxima is on the curve
px = bx * bx / (2 * bx - brx) + x1; // get the x maxima
}
if (y < 0 || y > 1) { // same as x
py = by * by / (2 * by - bry) + y1;
}
// create extent object and add extent
if (extent === undefined) {
extent = {};
extent.left = Math.min(x1, x3, px);
extent.top = Math.min(y1, y3, py);
extent.right = Math.max(x1, x3, px);
extent.bottom = Math.max(y1, y3, py);
} else { // use spplied extent and extend it to fit this curve
extent.left = Math.min(x1, x3, px, extent.left);
extent.top = Math.min(y1, y3, py, extent.top);
extent.right = Math.max(x1, x3, px, extent.right);
extent.bottom = Math.max(y1, y3, py, extent.bottom);
}
extent.width = extent.right - extent.left;
extent.height = extent.bottom - extent.top;
return extent;
}
Finding points along a cubic Bezier curve
This example finds an array of approximately evenly spaced points along a cubic Bezier curve.
It decomposes Path segments created with context.bezierCurveTo into points along that curve.
// Return: an array of approximately evenly spaced points along a cubic Bezier curve
//
// Attribution: Stackoverflow's @Blindman67
// Cite:
http: //stackoverflow.com/questions/36637211/drawing-a-curved-line-in-css-or-canvas-and-moving-circlealong-
it / 36827074 #36827074
// As modified from the above citation
//
// ptCount: sample this many points at interval along the curve
// pxTolerance: approximate spacing allowed between points
// Ax,Ay,Bx,By,Cx,Cy,Dx,Dy: control points defining the curve
//
function plotCBez(ptCount,pxTolerance,Ax,Ay,Bx,By,Cx,Cy,Dx,Dy){
var deltaBAx= Bx - Ax;
var deltaCBx = Cx - Bx;
var deltaDCx = Dx - Cx;
var deltaBAy = By - Ay;
var deltaCBy = Cy - By;
var deltaDCy = Dy - Cy;
var ax, ay, bx, by;
var lastX = -10000;
var lastY = -10000;
var pts = [{
x: Ax,
y: Ay
}];
for (var i = 1; i < ptCount; i++) {
var t = i / ptCount;
ax = Ax + deltaBAx * t;
bx = Bx + deltaCBx * t;
cx = Cx + deltaDCx * t;
ax += (bx - ax) * t;
bx += (cx - bx) * t;
//
ay = Ay + deltaBAy * t;
by = By + deltaCBy * t;
cy = Cy + deltaDCy * t;
ay += (by - ay) * t;
by += (cy - by) * t;
var x = ax + (bx - ax) * t;
var y = ay + (by - ay) * t;
var dx = x - lastX;
var dy = y - lastY;
if (dx * dx + dy * dy > pxTolerance) {
pts.push({
x: x,
y: y
});
lastX = x;
lastY = y;
}
}
pts.push({
x: Dx,
y: Dy
});
return (pts);
}
Finding points along a quadratic curve
This example finds an array of approximately evenly spaced points along a quadratic curve. It decomposes Path segments created with context.quadraticCurveTo into points along that curve.
// Return: an array of approximately evenly spaced points along a Quadratic curve
//
// Attribution: Stackoverflow's @Blindman67
// Cite:
http: //stackoverflow.com/questions/36637211/drawing-a-curved-line-in-css-or-canvas-and-moving-circlealong-
it / 36827074 #36827074
// As modified from the above citation
//
// ptCount: sample this many points at interval along the curve
// pxTolerance: approximate spacing allowed between points
// Ax,Ay,Bx,By,Cx,Cy: control points defining the curve
//
function plotQBez(ptCount,pxTolerance,Ax,Ay,Bx,By,Cx,Cy){
var deltaBAx= Bx - Ax;
var deltaCBx = Cx - Bx;
var deltaBAy = By - Ay;
var deltaCBy = Cy - By;
var ax, ay;
var lastX = -10000;
var lastY = -10000;
var pts = [{
x: Ax,
y: Ay
}];
for (var i = 1; i < ptCount; i++) {
var t = i / ptCount;
ax = Ax + deltaBAx * t;
ay = Ay + deltaBAy * t;
var x = ax + ((Bx + deltaCBx * t) - ax) * t;
var y = ay + ((By + deltaCBy * t) - ay) * t;
var dx = x - lastX;
var dy = y - lastY;
if (dx * dx + dy * dy > pxTolerance) {
pts.push({
x: x,
y: y
});
lastX = x;
lastY = y;
}
}
pts.push({
x: Cx,
y: Cy
});
return (pts);
}
Finding points along a line
This example finds an array of approximately evenly spaced points along a line. It decomposes Path segments created with context.lineTo into points along that line.
// Return: an array of approximately evenly spaced points along a line
//
// pxTolerance: approximate spacing allowed between points
// Ax,Ay,Bx,By: end points defining the line
//
function plotLine(pxTolerance, Ax, Ay, Bx, By) {
var dx = Bx - Ax;
var dy = By - Ay;
var ptCount = parseInt(Math.sqrt(dx * dx + dy * dy)) * 3;
var lastX = -10000;
var lastY = -10000;
var pts = [{
x: Ax,
y: Ay
}];
for (var i = 1; i <= ptCount; i++) {
var t = i / ptCount;
var x = Ax + dx * t;
var y = Ay + dy * t;
var dx1 = x - lastX;
var dy1 = y - lastY;
if (dx1 * dx1 + dy1 * dy1 > pxTolerance) {
pts.push({
x: x,
y: y
});
lastX = x;
lastY = y;
}
}
pts.push({
x: Bx,
y: By
});
return (pts);
}
Finding points along an entire Path containing
curves and lines
This example finds an array of approximately evenly spaced points along an entire Path.It decomposes all Path segments created with context.lineTo, context.quadraticCurveTo and/or context.bezierCurveTo into points along that Path.
// Path related variables
var A = {
x: 50,
y: 100
};
var B = {
x: 125,
y: 25
};
var BB = {
x: 150,
y: 15
};
var BB2 = {
x: 150,
y: 185
};
var C = {
x: 175,
y: 200
};
var D = {
x: 300,
y: 150
};
var n = 1000;
var tolerance = 1.5;
var pts;
// canvas related variables
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
canvas.width = 378;
canvas.height = 256;
// Tell the Context to plot waypoint in addition to
// drawing the path
plotPathCommands(ctx, n, tolerance);
// Path drawing commands
ctx.beginPath();
ctx.moveTo(A.x, A.y);
ctx.bezierCurveTo(B.x, B.y, C.x, C.y, D.x, D.y);
ctx.quadraticCurveTo(BB.x, BB.y, A.x, A.y);
ctx.lineTo(D.x, D.y);
ctx.strokeStyle = 'gray';
ctx.stroke();
// Tell the Context to stop plotting waypoints
ctx.stopPlottingPathCommands();
// Demo: Incrementally draw the path using the plotted points
ptsToRects(ctx.getPathPoints());
function ptsToRects(pts) {
ctx.fillStyle = 'red';
var i = 0;
requestAnimationFrame(animate);
function animate() {
ctx.fillRect(pts[i].x - 0.50, pts[i].y - 0.50, tolerance, tolerance);
i++;
if (i < pts.length) {
requestAnimationFrame(animate);
}
}
}
A plug-in that automatically calculates points along the path
This code modifies these Canvas Context's drawing commands so the commands not only draw the line or curve, but also create an array of points along the entire path:
- beginPath,
- moveTo,
- lineTo,
- quadraticCurveTo,
- bezierCurveTo.
Split bezier curves at position
This example splits cubic and bezier curves in two.
The function splitCurveAt splits the curve at position where 0.0 = start, 0.5 = middle, and 1 = end. It can split quadratic and cubic curves. The curve type is determined by the last x argument x4. If not undefined or null then it assumes the curve is cubic else the curve is a quadratic.
Example usage
Splitting quadratic bezier curve in two
var p1 = {
x: 10,
y: 100
};
var p2 = {
x: 100,
y: 200
};
var p3 = {
x: 200,
y: 0
};
var newCurves = splitCurveAt(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y)
var i = 0;
var p = newCurves
// Draw the 2 new curves
// Assumes ctx is canvas 2d context
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(p[i++], p[i++]);
ctx.quadraticCurveTo(p[i++], p[i++], p[i++], p[i++]);
ctx.quadraticCurveTo(p[i++], p[i++], p[i++], p[i++]);
ctx.stroke();
Splitting cubic bezier curve in two
var p1 = {
x: 10,
y: 100
};
var p2 = {
x: 100,
y: 200
};
var p3 = {
x: 200,
y: 0
};
var p4 = {
x: 300,
y: 100
};
var newCurves = splitCurveAt(0.5, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y)
var i = 0;
var p = newCurves
// Draw the 2 new curves
// Assumes ctx is canvas 2d context
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(p[i++], p[i++]);
ctx.bezierCurveTo(p[i++], p[i++], p[i++], p[i++], p[i++], p[i++]);
ctx.bezierCurveTo(p[i++], p[i++], p[i++], p[i++], p[i++], p[i++]);
ctx.stroke();
Trim bezier curve
This example show you how to trim a bezier.
The function trimBezier trims the ends off of the curve returning the curve fromPos to toPos. fromPos and toPos are in the range 0 to 1 inclusive, It can trim quadratic and cubic curves. The curve type is determined by the last x argument x4. If not undefined or null then it assumes the curve is cubic else the curve is a quadratic The trimmed curve is returned as an array of points. 6 points for quadratic curves and 8 for cubic curves.
example:
var trimBezier = function(fromPos, toPos, x1, y1, x2, y2, x3, y3, x4, y4) {
var quad, i, s, retBez;
quad = false;
if (x4 === undefined || x4 === null) {
quad = true; // this is a quadratic bezier
}
if (fromPos > toPos) { // swap is from is after to
i = fromPos;
fromPos = toPos
toPos = i;
}
// clamp to on the curve
toPos = toPos <= 0 ? 0 : toPos >= 1 ? 1 : toPos;
fromPos = fromPos <= 0 ? 0 : fromPos >= 1 ? 1 : fromPos;
if (toPos === fromPos) {
s = splitBezierAt(toPos, x1, y1, x2, y2, x3, y3, x4, y4);
i = quad ? 4 : 6;
retBez = [s[i], s[i + 1], s[i], s[i + 1], s[i], s[i + 1]];
if (!quad) {
retBez.push(s[i], s[i + 1]);
}
return retBez;
}
if (toPos === 1 && fromPos === 0) { // no trimming required
retBez = [x1, y1, x2, y2, x3, y3]; // return original bezier
if (!quad) {
retBez.push(x4, y4);
}
return retBez;
}
if (fromPos === 0) {
if (toPos < 1) {
s = splitBezierAt(toPos, x1, y1, x2, y2, x3, y3, x4, y4);
i = 0;
retBez = [s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]];
if (!quad) {
retBez.push(s[i++], s[i++]);
}
}
return retBez;
}
if (toPos === 1) {
if (fromPos < 1) {
s = splitBezierAt(toPos, x1, y1, x2, y2, x3, y3, x4, y4);
i = quad ? 4 : 6;
retBez = [s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]];
if (!quad) {
retBez.push(s[i++], s[i++]);
}
}
return retBez;
}
s = splitBezierAt(fromPos, x1, y1, x2, y2, x3, y3, x4, y4);
if (quad) {
i = 4;
toPos = (toPos - fromPos) / (1 - fromPos);
s = splitBezierAt(toPos, s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]);
i = 0;
retBez = [s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]];
return retBez;
}
i = 6;
toPos = (toPos - fromPos) / (1 - fromPos);
s = splitBezierAt(toPos, s[i++], s[i++], s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]);
i = 0;
retBez = [s[i++], s[i++], s[i++], s[i++], s[i++], s[i++], s[i++], s[i++]];
return retBez;
}
Length of a Cubic Bezier Curve (a close approximation)
- Method: The length of a cubic Bezier curve does not have a direct mathematical calculation. This "brute force" method finds a sampling of points along the curve and calculates the total distance spanned by those points.
- Accuracy: The approximate length is 99+% accurate using the default sampling size of 40.
// Return: Close approximation of the length of a Cubic Bezier curve
//
// Ax,Ay,Bx,By,Cx,Cy,Dx,Dy: the 4 control points of the curve
// sampleCount [optional, default=40]: how many intervals to calculate
// Requires: cubicQxy (included below)
//
function cubicBezierLength(Ax, Ay, Bx, By, Cx, Cy, Dx, Dy, sampleCount) {
var ptCount = sampleCount || 40;
var totDist = 0;
var lastX = Ax;
var lastY = Ay;
var dx, dy;
for (var i = 1; i < ptCount; i++) {
var pt = cubicQxy(i / ptCount, Ax, Ay, Bx, By, Cx, Cy, Dx, Dy);
dx = pt.x - lastX;
dy = pt.y - lastY;
totDist += Math.sqrt(dx * dx + dy * dy);
lastX = pt.x;
lastY = pt.y;
}
dx = Dx - lastX;
dy = Dy - lastY;
totDist += Math.sqrt(dx * dx + dy * dy);
return (parseInt(totDist));
}
// Return: an [x,y] point along a cubic Bezier curve at interval T
//
// Attribution: Stackoverflow's @Blindman67
// Cite:
http: //stackoverflow.com/questions/36637211/drawing-a-curved-line-in-css-or-canvas-and-moving-circlealong-
it / 36827074 #36827074// As modified from the above citation
//
// t: an interval along the curve (0<= t <= 1)
// ax,ay,bx,by,cx,cy,dx,dy: control points defining the curve
//
function cubicQxy(t, ax, ay, bx, by, cx, cy, dx, dy) {
ax += (bx - ax) * t;
bx += (cx - bx) * t;
cx += (dx - cx) * t;
ax += (bx - ax) * t;
bx += (cx - bx) * t;
ay += (by - ay) * t;
by += (cy - by) * t;
cy += (dy - cy) * t;
ay += (by - ay) * t;
by += (cy - by) * t;
return ({
x: ax + (bx - ax) * t,
y: ay + (by - ay) * t
});
}
Length of a Quadratic Curve
Given the 3 points of a quadratic curve the following function returns the length.
function quadraticBezierLength(x1, y1, x2, y2, x3, y3)
var a, e, c, d, u, a1, e1, c1, d1, u1, v1x, v1y;
v1x = x2 * 2;
v1y = y2 * 2;
d = x1 - v1x + x3;
d1 = y1 - v1y + y3;
e = v1x - 2 * x1;
e1 = v1y - 2 * y1;
c1 = (a = 4 * (d * d + d1 * d1));
c1 += (b = 4 * (d * e + d1 * e1));
c1 += (c = e * e + e1 * e1);
c1 = 2 * Math.sqrt(c1);
a1 = 2 * a * (u = Math.sqrt(a));
u1 = b / u;
a = 4 * c * a - b * b;
c = 2 * Math.sqrt(c);
return (a1 * c1 + u * b * (c1 - c) + a * Math.log((2 * u + u1 + c1) / (u1 + c))) / (4 * a1);
}
Derived from the quadratic bezier function F(t) = a * (1 - t)2 + 2 * b * (1 - t) * t + c * t2
ATutorialHub Related Guide
Comments (9)
User Comments

panduranga gupta
2021-07-05 07:03:13good website for learning and help me a lot

raju
2021-09-25 14:58:47The awsome website i am looking like for a long time, good work atutorialhub team keep doing

Shivani
2021-09-01 15:03:56Learning a lot from the courses present on atutorialhub. The courses are very well explained. Great experience

Harshitha
2021-09-10 15:05:45It is very helpful to students and easy to learn the concepts

Sowmya
2021-09-14 15:06:41Great job Tutorials are easy to understand Please make use of it

Zain Khan
2021-09-18 15:07:23Great content and customized courses.

Rudrakshi Bhatt
2021-09-09 15:08:10Well structured coursed and explained really well!

Pavana Somashekar
2021-09-11 15:09:08Good platform for beginners and learn a lot on this website

Sax
2021-09-25 19:35:50Nice website
Leave a Comment