Smooth Bezier on Canvas
const canvas = document.createElement('canvas');
const c = canvas.getContext('2d');
document.body.appendChild(canvas);
function draw() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
c.fillStyle = 'gray';
c.fillRect(0, 0, canvas.width, canvas.height);
c.strokeStyle = 'white';
c.lineWidth = 2;
c.beginPath();
c.moveTo(0, 0);
bezierSkin([10, 10, 210, 10, 10, 300, 210, 300], false);
bezierSkin([200, 10, 330, 10, 250, 300]);
bezierSkin(
Array(30)
.fill(0)
.map((a, b) => (b % 2 == 0) * 300 + Math.random() * 300)
);
c.stroke();
}
window.addEventListener('resize', draw);
draw();
// array of xy coords, closed boolean
function bezierSkin(bez, closed = true) {
const avg = calcAvgs(bez);
const leng = bez.length;
let i, n;
if (closed) {
c.moveTo(avg[0], avg[1]);
for (i = 2; i < leng; i += 2) {
n = i + 1;
c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
}
c.quadraticCurveTo(bez[0], bez[1], avg[0], avg[1]);
} else {
c.moveTo(bez[0], bez[1]);
c.lineTo(avg[0], avg[1]);
for (i = 2; i < leng - 2; i += 2) {
n = i + 1;
c.quadraticCurveTo(bez[i], bez[n], avg[i], avg[n]);
}
c.lineTo(bez[leng - 2], bez[leng - 1]);
}
}
// create anchor points by averaging the control points
function calcAvgs(p) {
const avg = [];
const leng = p.length;
let prev;
for (var i = 2; i < leng; i++) {
prev = i - 2;
avg.push((p[prev] + p[i]) / 2);
}
// close
avg.push((p[0] + p[leng - 2]) / 2);
avg.push((p[1] + p[leng - 1]) / 2);
return avg;
}
Being able to draw smooth lines that connect arbitrary points is something that I find myself needing very frequently. This is a port of an old old snippet of mine that does just that. By averaging control points of a quadratic bezier curve we ensure that our resulting Bezier curves are always smooth.
It would be very cool if html5 canvas implemented the Catmull Rom Spline but it unfortunately does not. The wonderful Raphael library used to have support for it.