Bezier math

by Vasily Liaskovsky

HTML

<div id="demo">
  <input type="text" id="path" placeholder="SVG path" size="50"/>
  <button type="button" id="random">Random</button>
  <button type="button" id="apply">Apply</button>
  <p><a href="https://github.com/blr-Oliver/bezier">To the repo</a></p>
  <svg viewBox="-50 -50 200 200"></svg>
</div>

CSS

#demo{
  text-align: center;
}
#demo svg{
  overflow: visible;
  margin: 20px auto 0;
  display: block;
  width: 400px;
  border: 1px dashed black;
}

.key-points circle {
  fill: rgba(255, 0, 0, 0.7);
  stroke: none;
}

.key-points line {
  stroke-width: 0.5;
  stroke-linecap: round;
  stroke: red;
}

.curve {
  fill: none;
  stroke-width: 2;
  stroke: rgba(0, 0, 0, 0.5);
}

.box {
  fill: rgba(0, 192, 255, 0.2);
  stroke: none;
}

JavaScript

const SVG_NS = "http://www.w3.org/2000/svg";

window.addEventListener('load', function(){
  var
    applyHandler = () => render(document.querySelector('svg'), document.querySelector('#path').value),
    randomHandler = () => (document.querySelector('#path').value = randomCurve().toPath());

  document.querySelector('#random').addEventListener('click', randomHandler);
  document.querySelector('#apply').addEventListener('click', applyHandler);
  
  randomHandler();
  applyHandler();

  //M -48.58 -28.96 C -231.13 -73.26 -76.17 -62.44 -51.66 -40.32
});

function randomCurve(){
  var rs = () => rnd(0, 100), rl = () => rnd(-50, 150);
  return new Bezier(...[rs(), rs(), rl(), rl(), rl(), rl(), rs(), rs()].map(x => +x.toFixed(2)));
}

function rnd(from, to){
  return Math.random() * (to - from) + from;
}

function render(svg, path){
  var
    bezier = Bezier.fromPath(path),
    bbox = bezier.getBBox(),
    d = Math.hypot(bbox.width, bbox.height);

  removeAll(svg);
  appendAll(svg,
      createSVGElement('path', {d: path}, 'curve'),
      createSVGElement('rect', bbox, 'box'),
      appendAll(createSVGElement('g', 0, 'key-points'),
          ...createTangents(bezier, 0, 1, d/2),
          ...createTangents(bezier, 1, 0, d/2),
          ...createTangents(bezier, 1, 1, d/2),
          ...createTangents(bezier, -1, 1, d/2)
      )
  )
}

function removeAll(parent, node){
  while(node = parent.lastChild)
    node.remove();
}

function appendAll(parent){
  [].slice.call(arguments, 1).forEach(node => parent.appendChild(node));
  return parent;
}

function createTangents(bezier, tx, ty, d){
  var points = bezier.tangentPoints(tx, ty).filter(t => !!t).map(t => bezier.at(t));
  var k = d/2 / Math.hypot(tx, ty);
  return [
          ...points.map(p => createSVGElement('circle', {cx: p[0], cy: p[1], r: 1.2})),
          ...points.map(p => createSVGElement('line', {x1: p[0] - k*tx, x2: p[0] + k*tx, y1: p[1] - k*ty, y2: p[1] + k*ty}))
        ];
}

function createSVGElement(name,...