<!-- Original code here https://gist.github.com/twolfson/5b1a1ed21a77bc0fdd215e80ee7225cb -->
.container
  p Click and drag a circle to see the our distance to segment update!
  svg#scene(width="400", height="400", viewBox="0 0 400 400", xmlns="https://www.w3.org/2000/svg")
    //- DEV: We put our line first so it's hidden behind our vertex z-indicies
    line#line--ab.line(x1="120", y1="170", x2="280", y2="170",
      stroke-width="10", stroke="#000")
    line#line--cx.line(x1="60", y1="260", x2="120", y2="170",
      stroke-width="5", stroke="#999", stroke-dasharray="5,5")
    circle#vertex--a.vertex(cx="120", cy="170", r="20")
    circle#vertex--b.vertex(cx="280", cy="170", r="20")
    circle#vertex--c.vertex(cx="60", cy="260", r="20")
View Compiled
body {
  /* https://github.com/corysimmons/typographic/blob/2.9.3/scss/typographic.scss#L34 */
  font-family: 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif';
}

/* Medium screenshot specific offsets, no height seems to be present */
@media screen and (width: 1024px) {
  .container {
    width: 768px; /* 1024 - 2*(8 - 120 - ~6) -- visual inspection of orange dot in Medium */
    margin: 0 auto;
    padding-top: 51px; /* 43px for Codepen text + 8px gutter width */
  } 
}

#scene {
  border: 1px solid #000;
}

.vertex {
  cursor: pointer;
  stroke: #000;
  stroke-width: 5px;
}

#vertex--a {
  fill: orange;
}
#vertex--b {
  fill: blue;
}
#vertex--c {
  fill: limegreen;
}
const Draggabilly = window.Draggabilly;
document.addEventListener('DOMContentLoaded', function handleReady () {
  // Resolve our circle
  // TODO: Add assertion for element resolve
  let sceneEl = document.querySelector('#scene');
  let vertexAEl = sceneEl.querySelector('#vertex--a');
  let vertexBEl = sceneEl.querySelector('#vertex--b');
  let vertexCEl = sceneEl.querySelector('#vertex--c');
  let lineABEl = sceneEl.querySelector('#line--ab');
  let lineCXEl = sceneEl.querySelector('#line--cx');

  // Add our drag bindings
  function bindCircleDraggabilly(circleEl) {
    // Initialize our bindings
    let draggie = new Draggabilly(circleEl);

    // Update our bindings to support SVG
    // https://github.com/desandro/draggabilly/blob/v2.1.1/draggabilly.js#L266
    // https://github.com/desandro/draggabilly/blob/v2.1.1/draggabilly.js#L184-L194
    draggie._getPosition = function() {
      let x = parseFloat(this.element.getAttribute('cx'), 10);
      let y = parseFloat(this.element.getAttribute('cy'), 10);
      // Clean up 'auto' or other non-integer values
      this.position.x = isNaN(x) ? 0 : x;
      this.position.y = isNaN(y) ? 0 : y;
    };
    // https://github.com/desandro/draggabilly/blob/v2.1.1/draggabilly.js#L424-L427
    draggie.positionDrag = function () {
      this.element.setAttribute('cx', this.startPosition.x + this.dragPoint.x);
      this.element.setAttribute('cy', this.startPosition.y + this.dragPoint.y);
    };
    // https://github.com/desandro/draggabilly/blob/v2.1.1/draggabilly.js#L418-L422
    draggie.setLeftTop = function () {
      this.element.setAttribute('cx', this.position.x);
      this.element.setAttribute('cy', this.position.y);
    };

    // Return our overridden binding
    return draggie;
  }
  let vertexADraggie = bindCircleDraggabilly(vertexAEl);
  let vertexBDraggie = bindCircleDraggabilly(vertexBEl);
  let vertexCDraggie = bindCircleDraggabilly(vertexCEl);

  // Eagerly calculate our positions so we can have a shared redraw
  vertexADraggie._getPosition();
  vertexBDraggie._getPosition();
  vertexCDraggie._getPosition();

  // When our vertices move, update our scene
  // DEV: We could be more intelligent about what updated but it's saner to perform a declarative render
  let aVertex = vec2.create();
  let bVertex = vec2.create();
  let cVertex = vec2.create();
  let abVector = vec2.create();
  let acVector = vec2.create();
  function updateScene() {
    // Update our lines
    lineABEl.setAttribute('x1', vertexADraggie.position.x);
    lineABEl.setAttribute('y1', vertexADraggie.position.y);
    lineABEl.setAttribute('x2', vertexBDraggie.position.x);
    lineABEl.setAttribute('y2', vertexBDraggie.position.y);
    lineCXEl.setAttribute('x1', vertexCDraggie.position.x);
    lineCXEl.setAttribute('y1', vertexCDraggie.position.y);

    // Unwrap our positions into vectors
    vec2.set(aVertex, vertexADraggie.position.x, vertexADraggie.position.y);
    vec2.set(bVertex, vertexBDraggie.position.x, vertexBDraggie.position.y);
    vec2.set(cVertex, vertexCDraggie.position.x, vertexCDraggie.position.y);

    // Perform our distance to segment calculations
    // Resolve the vectors between our vertices and our point
    // DEV: This overwrites our reusable variable values
    vec2.sub(abVector, bVertex, aVertex);
    vec2.sub(acVector, cVertex, aVertex);

    // Compare how much our vectors overlap
    /*
      3 scenarios (simplified to exclude multiple axises):
      * | are projections, \ | / are closest lines

      "Behind" A
      C
       \
      **A--------B

      Between A and B
          C
          |
      A****----B

      "Ahead of" B
                 C
                /
      A--------B**
    */
    // cos(θ) = percentage of AC that projects onto AB
    //   https://en.wikipedia.org/wiki/Dot_product#/media/File:Dot_Product.svg
    // cos(θ) * |AC|/|AB| = ratio for how much AC overlaps AB
    // acAbRatio = |AC|*|AB|*cos(θ) / |AB|^2 = |AC|*cos(θ)/|AB| = cos(θ) * |AC|/|AB|
    //   See deeper proof in https://gist.github.com/twolfson/207556d7ac0cd5d04fa283f6062841ab#finding-the-shortest-distance-from-a-point-to-asegment
    let baSquaredLength = vec2.squaredLength(abVector);
    let acAbRatio = vec2.dot(acVector, abVector) / baSquaredLength;

    // If our ratio/projection is "behind" AB, then update our line to point to A
    if (acAbRatio < 0.0) {
      lineCXEl.setAttribute('x2', vertexADraggie.position.x);
      lineCXEl.setAttribute('y2', vertexADraggie.position.y);
    // Otherwise, if our ratio/projection is "ahead of" AB, then update our line to point to B
    } else if (acAbRatio > 1.0) {
      lineCXEl.setAttribute('x2', vertexBDraggie.position.x);
      lineCXEl.setAttribute('y2', vertexBDraggie.position.y);
    // Otherwise (we're projecting between A and B), perform vector addition to find our projection
    } else {
      let cxX = aVertex[0] + acAbRatio * abVector[0];
      let cxY = aVertex[1] + acAbRatio * abVector[1];
      lineCXEl.setAttribute('x2', cxX);
      lineCXEl.setAttribute('y2', cxY);
    }
  }
  vertexADraggie.on('dragMove', updateScene);
  vertexBDraggie.on('dragMove', updateScene);
  vertexCDraggie.on('dragMove', updateScene);
});
Rerun