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;...