JSFiddle - React, Tailwind, and code Playground

by Keith Henry

HTML

<svg viewBox="-500 -50 1000 900" xmlns="http://www.w3.org/2000/svg">
	<circle id="radius" cy="336.02" cx="872.068" r="618.765"></circle>
	<line x1="375.64105224609375" y1="705.2225341796875" x2="149.9835662841797" y2="706.24609375"></line><line x1="149.9835662841797" y1="706.24609375" x2="-355.71612548828125" y2="283.26336669921875"></line><line x1="-355.71612548828125" y1="283.26336669921875" x2="-630.5037841796875" y2="720.3171997070312"></line><path d="M 375.64105224609375 705.2225341796875 C 149.9835662841797 706.24609375 -355.71612548828125 283.26336669921875 -630.5037841796875 720.3171997070312"></path>
	<circle id="closest" r="7" cy="705.223" cx="375.52"></circle>
<circle class="drag" r="10" cx="375.64105224609375" cy="705.2225341796875"></circle><circle class="drag" r="10" cx="-630.5037841796875" cy="720.3171997070312"></circle><circle class="drag" r="10" cx="149.9835662841797" cy="706.24609375"></circle><circle class="drag" r="10" cx="-355.71612548828125" cy="283.26336669921875"></circle></svg>

CSS

html, body {
		background:#eee; margin:0;
		user-select:none; -moz-user-select:none; -webkit-user-select:none;
		height:100%; overflow:hidden;
	}
	p { margin:0.5em; text-align:center }
	svg { position:absolute; bottom:5%; left:5%; width:90%; height:90%; }
	canvas { position:fixed; bottom:5%; right:5%; width:40%; height:10%; }
	svg { background:#fff; border:1px solid #ccc }
	svg path { fill:none; stroke:#000; stroke-width:3px; vector-effect:non-scaling-stroke; stroke-linecap:round; }
	svg circle { opacity:0.3; fill:#ff0; stroke:#630; vector-effect:non-scaling-stroke }
	svg .drag { cursor:move }
	svg line { stroke:#900; opacity:0.5; stroke-width:1px; vector-effect:non-scaling-stroke; stroke-dasharray:4; }
	#footer {
		position:absolute; bottom:0.5em; margin-bottom:0;
		width:40em; margin-left:-20em; left:50%; color:#666;
		font-style:italic; font-size:85%
	}
	#dragcatch { position:absolute; left:0; right:0; top:0; bottom:0; z-index:-1 }
	#closest { opacity:1; fill:#f00 }
	#radius { fill:#ccc; }

JavaScript

var svg   = document.querySelector('svg'),
	    path  = document.querySelector('path');
	var svgNS = svg.namespaceURI;
	var pt    = svg.createSVGPoint();
	draggablePath(path,10);
	makeDraggable(svg.querySelectorAll('.drag'));

	// http://jointjs.com/blog/get-transform-to-element-polyfill.html
	SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) {
		return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM());
	};

	function draggablePath(p,r){
		function dot(seg,add){
			var x = 'x'+add, y = 'y'+add;
			var c = document.createElementNS(svgNS,'circle');
			c.setAttribute('class','drag');
			c.setAttribute('r',r);
			c.setAttribute('cx',seg[x]);
			c.setAttribute('cy',seg[y]);
			p.parentNode.appendChild(c);
			c.addEventListener('dragged',function(){
				seg[x] = c.getAttribute('cx');
				seg[y] = c.getAttribute('cy');
				fireEvent(p,'changed');
			},true);
			return c;
		}
		var circles=[];
		for (var i=0,segs=p.pathSegList,len=segs.numberOfItems;i<len;++i){
			var s = segs.getItem(i);
			switch(s.pathSegType){
				case SVGPathSeg.PATHSEG_CURVETO_CUBIC_ABS:
					circles.push(dot(s,'1',i));
					circles.push(dot(s,'2',i));
					// no break; flow through
				case SVGPathSeg.PATHSEG_MOVETO_ABS:
				case SVGPathSeg.PATHSEG_LINETO_ABS:
					circles.push(dot(s,'',i));
				break;
			}
		}
		for (var i=1,len=circles.length;i<len;++i){
			var line = document.createElementNS(svgNS,'line');
			p.parentNode.insertBefore(line,p);
			(function(c1,c2,l){
				c1.addEventListener('dragged',function(){
					l.setAttribute('x1',c1.cx.baseVal.value);
					l.setAttribute('y1',c1.cy.baseVal.value);
				},false);
				c2.addEventListener('dragged',function(){
					l.setAttribute('x2',c2.cx.baseVal.value);
					l.setAttribute('y2',c2.cy.baseVal.value);
				},false);
				fireEvent(c1,'dragged');
				fireEvent(c2,'dragged');
			})(circles[i-1],circles[i],line);
		}
	}

	function cursorPoint(evt){
		pt.x = evt.clientX;...