Catmull Rom to Bezier SVG
// from:
// https://github.com/DmitryBaranovskiy/raphael/blob/b5fdbdc9850827eb49622cd0bc81b26025f8128a/dev/raphael.core.js#L1019
// and:
// http://schepers.cc/getting-to-the-point
function catmullRom2bezier(crp, close) {
const d = [];
for (let i = 0, iLen = crp.length; iLen - 2 * !close > i; i += 2) {
const p = [
{ x: crp[i - 2], y: crp[i - 1] },
{ x: crp[i], y: crp[i + 1] },
{ x: crp[i + 2], y: crp[i + 3] },
{ x: crp[i + 4], y: crp[i + 5] }
];
if (close) {
if (!i) {
p[0] = { x: crp[iLen - 2], y: crp[iLen - 1] };
} else if (iLen - 4 == i) {
p[3] = { x: crp[0], y: crp[1] };
} else if (iLen - 2 == i) {
p[2] = { x: crp[0], y: crp[1] };
p[3] = { x: crp[2], y: crp[3] };
}
} else {
if (iLen - 4 == i) {
p[3] = p[2];
} else if (!i) {
p[0] = { x: crp[i], y: crp[i + 1] };
}
}
d.push([
'C',
(-p[0].x + 6 * p[1].x + p[2].x) / 6,
(-p[0].y + 6 * p[1].y + p[2].y) / 6,
(p[1].x + 6 * p[2].x - p[3].x) / 6,
(p[1].y + 6 * p[2].y - p[3].y) / 6,
p[2].x,
p[2].y
]);
}
// really for demo purposes:
// draw a tiny rect if less than 2 points
console.log(d.length);
if (d.length < 2) {
const x = crp[0];
const y = crp[1];
return `M ${x} ${y} h 1 v 1 h -1 v-1`;
// draw a line if 2 points
} else if (d.length === 2) {
return `M ${crp[0]} ${crp[1]} L ` + crp.flat().join` `;
}
// draw curves if 3 or more (raphael only ever did this, other numbers of points
// were considered invalid)
return `M ${crp[0]} ${crp[1]}` + d.flat().join` `;
}
// try it out:
const pathCommands = catmullRom2bezier([
100, 100, 200, 100, 200, 300, 170, 150, 120, 180, 120, 120
], true)
const svg = `
click 3 or more times anywhere<br>
<button>clear</button>
<svg>
<path d="M 100 100 ${pathCommands}" stroke="blue" fill="none" />
</svg>
<style>
svg {
position:absolute;
left:0;top:0;
overflow:visible;
pointer-events:none;
}
</style>
`;
const div = document.body.appendChild(
document.createElement('div')
);
div.innerHTML = svg;
const path = div.querySelector('path')
let pnts = [];
function isButton(e) {
if (e.target.tagName === 'BUTTON') {
pnts = []
path.setAttribute('d', 'M 0 0L0 0');
return true;
}
}
document.addEventListener('click', e => {
if (isButton(e)) return;
pnts.push(e.clientX, e.clientY);
path.setAttribute('d', catmullRom2bezier(pnts, true))
});
document.addEventListener('touchend', e => {
if (isButton(e)) return;
pnts.push(e.touchs[0].clientX, e.touchs[0].e.clientY);
path.setAttribute('d', catmullRom2bezier(pnts, true))
});
Raphaël was my first deep dive into SVG. Back then, only VML was supported in older IE versions, so using Raphaël was key for tons of commercial work I did (Raphaël generated VML as well as SVG). This snippet is the Catmull Rom code from Raphaël. I’m pretty sure I didn’t know what a Catmull Rom spline was until I found out that using `R` in a path wasn’t supported natively in SVG… and was actually some magic thing that existed in Raphaël alone.