Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <svg id="svg" viewBox="30 30 370 370">

  <!--Only C commands!!-->
  <path id="control" d="M220.284, 274.127 C222.69, 292.729 236.31, 327.393 222.104, 343.729 C199.204, 370.062 204.648, 299.718 205.313, 294.403 C206.37599999999998, 285.913 206.538, 234.938 186.057, 258.766 C184.554, 260.516 151.532, 316.948 149.545, 292.699 C148.943, 285.361 165.247, 263.296 156.07, 258.172 C145.273, 252.144 129.462, 266.782 122.903, 273.109 C118.864, 277 88, 308.363 84.866, 290.225 C81.299, 269.623 109.589, 261.834 122.985, 254.777 C133.129, 249.433 155.995, 238.545 154.779, 223.568 C154.115, 215.396 135.042, 213.398 129, 210.94 C97.966, 198.316 176.171, 199.402 160.49, 175.281 C151.785, 161.888 133.318, 160.47 123.697, 148.435 C114.608, 137.066 142.599, 137.991 147.305, 139.401 C165.688, 144.911 174.376, 144.194 180.763, 125.275 C185.332, 111.74 201.271, 101.134 201.022, 122.936 C200.963, 128.247 194.075, 156.565 204.61, 157.111 C217.97, 157.803 224.695, 133.092 227.11, 124.507 C230.276, 113.253 234.811, 76.297 254.02, 81.824 C269.047, 86.148 242.151, 128.931 244.391, 140.223 C250.991, 173.48 296.982, 116.8745 303.12, 111.026 C311.089, 103.41 324.251, 91.402 336.431, 96.705 C352.83, 103.845 351.81, 131.343 341.617, 142.953 C330.393, 155.737 256.125, 167.755 283.709, 195.747 C289.606, 201.732 326.715, 216.353 321.07, 227.267 C313.798, 241.327 288.523, 229.944 276.784, 232.905 C259.242, 237.334 270.387, 261.316 273.851, 271.943 C278.42, 285.957 275.291, 320.994 258.381, 294.742 C250.602, 282.665 254.705, 257.742 239.739, 250.72 C224.742, 243.687 218.352, 268.723 221.061, 279.048Z"/>
  <path id="track"></path>
</svg>
              
            
!

CSS

              
                body{background:radial-gradient( circle 50vw, #2aa4d5, #030633); height:100vh;}
svg {
  width: 100vw;
  height: 100vh;
}
polyline {
  pointer-events: none;
  stroke: red;
  stroke-width: 0.5;
  fill: none;
}
path {
  fill: white;
  fill-opacity:.25;
  stroke: white;
  stroke-width: 3;
}
circle {
  fill: #f00;
  cursor: pointer;
  stroke:black;
  paint-order:stroke;
}

#control {
  fill:none;
  stroke: #aaf;
  stroke-opacity:.5;
  stroke-width: 1;
}

              
            
!

JS

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

//get the d attribute of the control curve 
let d = control.getAttribute("d");
// split the d attribute in an array of commands
let commands = getCommandsArray(d);



///////////////////////////////////
let ry = [];
//for every command I ignore the first ittem (a letter command)
//the rest of the command is splitted in point objects {x,y} and saved in the ry array
commands.map(c=>{
  for(let i = 1; i < c.length; i+=2){
    let o = {}
    o.x = Number(c[i]);
    o.y = Number(c[i + 1]);
    
    ry.push(o)
  }
})


ry.shift();

// remove the last 2 items of the array and add them at the begining
let last2 = ry.splice(-2,2);
ry.unshift(...last2);
////////////////////////////////////


let groups = [];
let draggable = null;
let rotatable = null;
let theGroup = null;

let last = {}


class Group {
  // builds a group of a point and 2 control points
  constructor(cp2, pos, cp1) {
    //
    // cp2 ◉----●----◉ cp1
    //         pos
    
    
    this.pos = pos;//absolute coords
    this.cp2 = cp2;//absolute coords
    this.rel_cp2 = {//relative coords
      x: cp2.x - pos.x,
      y: cp2.y - pos.y
    };
    //distance between the center point (pos) and the control point cp2
    this.cp2_dist = dist(pos, cp2);

    this.cp1 = cp1;//absolute coords
    this.rel_cp1 = {//relative coords
      x: cp1.x - pos.x,
      y: cp1.y - pos.y
    };
    //distance between the center point (pos) and the control point cp1
    this.cp1_dist = dist(pos, cp1);

    this.Angle = Math.atan2(this.cp2.y, this.cp2.x);//initial tilt
    this.angle = 0; // initial rotation
    this.draw();// draw the croup and the elements
    this.addEvents();
  }

  draw() {
    // draw the group 
    this.eG = drawSVGelmt(
      { transform: `translate(${this.pos.x},${this.pos.y})` },
      "g",
      svg
    );
    // draw the element for the connector
    this.eConnector = drawSVGelmt(
      {
        points: `${this.rel_cp2.x},${this.rel_cp2.y}
                 0,0
                 ${this.rel_cp1.x},${this.rel_cp1.y}`
      },
      "polyline",
      this.eG
    );
    
    // draw the element for the bézier point
    this.ePoint = drawSVGelmt({ r: 3 }, "circle", this.eG);
    //draw the element for the control point cp2
    this.eCp2 = drawSVGelmt(
      { r: 2, cx: this.rel_cp2.x, cy: this.rel_cp2.y },
      "circle",
      this.eG
    );
    //draw the element for the control point cp1
    this.eCp1 = drawSVGelmt(
      { r: 2, cx: this.rel_cp1.x, cy: this.rel_cp1.y },
      "circle",
      this.eG
    );
    
    
  }

  update() {
    this.eCp1.setAttributeNS(null, "cx", this.rel_cp1.x);
    this.eCp1.setAttributeNS(null, "cy", this.rel_cp1.y);
    this.eCp2.setAttributeNS(null, "cx", this.rel_cp2.x);
    this.eCp2.setAttributeNS(null, "cy", this.rel_cp2.y);

    let attrPoints = `${this.rel_cp2.x},${this.rel_cp2.y}
                      0,0
                      ${this.rel_cp1.x},${this.rel_cp1.y}`;

    this.eConnector.setAttributeNS(null, "points", attrPoints);
    
 
  }

  addEvents() {
    this.ePoint.addEventListener("mousedown", e => {
      draggable = true;
      theGroup = this;

    });

    this.eCp1.addEventListener("mousedown", e => {
      rotatable = 1;
      theGroup = this;

    });
    this.eCp2.addEventListener("mousedown", e => {
      rotatable = 2;
      theGroup = this;

    });
  }
}


//groups.push(new Group(pos, cp2, cp1));

for (let i = 0; i < ry.length; i += 3) {
  groups.push(new Group( ry[i], ry[i+1], ry[i + 2]));
}

updateCurve(groups, track);




svg.addEventListener("mouseup", () => {
  draggable = false;
  rotatable = false;
});


svg.addEventListener("mousemove", e => {
  if (draggable || rotatable) {
    let angle = theGroup.angle;
    m = getMousePositionSVG(e);

    if (draggable) {
      
      last.x = theGroup.pos.x;
      last.y = theGroup.pos.y;
      
      theGroup.pos.x = m.x;
      theGroup.pos.y = m.y;
      
      let dx = theGroup.pos.x - last.x;
      let dy = theGroup.pos.y - last.y;
      theGroup.cp1.x += dx;
      theGroup.cp1.y += dy;
      
      theGroup.cp2.x += dx;
      theGroup.cp2.y += dy;
      
    }

    if (rotatable) {
      if (rotatable == 1) {
        theGroup.cp1_dist = dist(theGroup.pos, m);
        theGroup.cp1.x = m.x;
        theGroup.cp1.y = m.y;

        angle =
          Math.PI -
          theGroup.angle +
          Math.atan2(m.y - theGroup.pos.y, m.x - theGroup.pos.x);

        theGroup.rel_cp1.x =
          theGroup.cp1_dist * Math.cos(Math.PI + theGroup.angle);
        theGroup.rel_cp1.y =
          theGroup.cp1_dist * Math.sin(Math.PI + theGroup.angle);

        theGroup.rel_cp2.x = theGroup.cp2_dist * Math.cos(theGroup.angle);
        theGroup.rel_cp2.y = theGroup.cp2_dist * Math.sin(theGroup.angle);
        
        
        theGroup.cp2 = getReflected(theGroup.cp1,theGroup.pos,theGroup.cp2_dist);
        

        
        
      }
      
      
      
      if (rotatable == 2) {
        theGroup.cp2_dist = dist(theGroup.pos, m);
        theGroup.cp2.x = m.x;
        theGroup.cp2.y = m.y;

        angle =
          -theGroup.angle +
          Math.atan2(m.y - theGroup.pos.y, m.x - theGroup.pos.x);
        
       
        
        

        theGroup.rel_cp2.x =
          theGroup.cp2_dist * Math.cos(theGroup.angle);
        theGroup.rel_cp2.y =
          theGroup.cp2_dist * Math.sin(theGroup.angle);

        theGroup.rel_cp1.x = theGroup.cp1_dist * Math.cos(Math.PI + theGroup.angle);
        theGroup.rel_cp1.y = theGroup.cp1_dist * Math.sin(Math.PI + theGroup.angle);
        
        
        theGroup.cp1 = getReflected(theGroup.cp2,theGroup.pos,theGroup.cp1_dist);
      }
      
      
      
      
      

      theGroup.angle = angle;
      
      

    }

    theGroup.eG.setAttributeNS(
      null,
      "transform",
      `translate(${theGroup.pos.x},${theGroup.pos.y}) rotate(${angle *
        180 /
        Math.PI})`
    );

    theGroup.update();
    
     updateCurve(groups, track);     
    
  }
});











function updateCurve(groups, path) {
  let d = "";

  d += `M${groups[0].pos.x},${groups[0].pos.y} `;
  for (let i = 1; i < groups.length; i++) {
    let p = groups[i];
    let prev = groups[i - 1];

    d += `C${prev.cp1.x},${prev.cp1.y} 
       ${p.cp2.x},${p.cp2.y} 
       ${p.pos.x},${p.pos.y} `;
  }

  let last = groups[groups.length - 1];
  d += `C${last.cp1.x},${last.cp1.y} 
       ${groups[0].cp2.x},${groups[0].cp2.y} 
       ${groups[0].pos.x},${groups[0].pos.y} `;

  console.log(d);
  
  path.setAttributeNS(null, "d", d);
}










function drawSVGelmt(o, tag, parent) {
  var elmt = document.createElementNS(SVG_NS, tag);
  for (var name in o) {
    if (o.hasOwnProperty(name)) {
      elmt.setAttributeNS(null, name, o[name]);
    }
  }
  parent.appendChild(elmt);
  return elmt;
}

function updateSVGelmt(o, element) {
  for (var name in o) {
    if (o.hasOwnProperty(name)) {
      element.setAttributeNS(null, name, o[name]);
    }
  }
}

function getMousePositionSVG(event) {
  var point = svg.createSVGPoint();
  point.x = event.clientX;
  point.y = event.clientY;
  point = point.matrixTransform(svg.getScreenCTM().inverse());
  return point;
}

function dist(p1, p2) {
  let dx = p2.x - p1.x;
  let dy = p2.y - p1.y;
  return Math.sqrt(dx * dx + dy * dy);
}


function getReflected(p,c,r){
  //p: point 
  //center
  //r distance from center of the reflected point
let dx = p.x - c.x;
let dy = p.y - c.y;
let rot = Math.atan2(dy,dx);
let o = {}
o.x = c.x + r*Math.cos(Math.PI+rot);
o.y = c.y + r*Math.sin(Math.PI+rot);

return o;//the reflected point
}

              
            
!
999px

Console