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 is required to process package imports. If you need a different preprocessor remove all packages first.

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

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 Settings

Here you can Sed posuere consectetur est at lobortis. Donec ullamcorper nulla non metus auctor fringilla. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec id elit non mi porta gravida at eget metus. Praesent commodo cursus magna, vel scelerisque nisl consectetur et.

HTML

              
                <div id = comment>
Hi, you
</div>
              
            
!

CSS

              
                body {
  font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
  background-color : #000;
  margin: 0;
  padding: 0;
  border-width: 0;
}

svg {
  position: absolute;
  top: 0;
  left: 0;
}

.marker {
  transition: fill 0.5s, r 0.5s;
}

#comment {
  position: absolute;
  background-color: rgba(0, 0, 0, 0.5);
  color: white;
  border-radius: 10%;
  text-align: center;
  z-index: 10;

}

.line{
  transition: fill 2s, stroke 2s;
}

.parallelogram {
  transition: fill 3s, stroke 3s;
}
              
            
!

JS

              
                "use strict";

window.addEventListener("load",function() {

  let svg;
  let canv;
  let ctxSlider;
  let slider;
  let maxx, maxy;
  let selected, requested, request;
  let animState;

  let u, v; // unit vectors for the array of triangles
  let orgx, orgy;
  let nbu, nbv;
  let grid;
  let vertices;
  let comment;
  let lines; // arrays for lines of 2 kinds
  let parallelograms;

  let picked; // vertex chosen for example triangle

  const NS = "http://www.w3.org/2000/svg";
  const lineOff = 'rgba(0, 0, 0, 0)';
  const lineRed = 'rgba(255, 0, 0, 1)';
  const lineBlue = 'rgba(0, 0, 255, 1)';
// shortcuts for Math.…

  const mrandom = Math.random;
  const mfloor = Math.floor;
  const mround = Math.round;
  const mceil = Math.ceil;
  const mabs = Math.abs;
  const mmin = Math.min;
  const mmax = Math.max;

  const mPI = Math.PI;
  const mPIS2 = Math.PI / 2;
  const m2PI = Math.PI * 2;
  const msin = Math.sin;
  const mcos = Math.cos;
  const matan2 = Math.atan2;

  const mhypot = Math.hypot;
  const msqrt = Math.sqrt;

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  function alea (min, max) {
// random number [min..max[ . If no max is provided, [0..min[

    if (typeof max == 'undefined') return min * mrandom();
    return min + (max - min) * mrandom();
  }

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  function intAlea (min, max) {
// random integer number [min..max[ . If no max is provided, [0..min[

    if (typeof max == 'undefined') {
      max = min; min = 0;
    }
    return mfloor(min + (max - min) * mrandom());
  } // intAlea

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  function randomElement(array) {
    return array[intAlea(array.length)];
  } // randomElement

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function roundedValue (x, nb) {
/* nb integer >= 0 : places after decimal point (no decimal point if == 0)
non-significant 0 removed  after decimal point, decimal point removed too if only followed by zeroes
returns string
*/
  let s;
  let x0 = x;
  let sign ='';
  for (let k = 0; k < nb; ++k) x *= 10;
  s = mround(x).toString();
  if (nb == 0) return s;
  if (s.indexOf('e') != -1) return x0.toString(); // too lazy to manage scientific notation
  if (s.charAt(0) == '-') { // take sign apart if any
    sign = '-';
    s = s.substring(1);
  } // if < 0
  while (s.length < nb + 1 ) s = '0' + s; // add 0 to the left if less than nb figures
  s = s.substring(0, s.length - nb) + '.' + s.substring(s.length - nb);
/* remove useless zeroes */
  while (s.charAt(s.length - 1) == '0') s = s.substring(0, s.length - 1);
/* remove final '.' */
  if (s.charAt(s.length - 1) == '.') s = s.substring(0, s.length - 1);
  return sign + s;
} // roundedValue

//-----------------------------------------------------------------------------

function dimensionSlider() {

/* slider and other stuff */

  slider.wRef = maxx * 0.4;
  slider.hRef = maxy * 0.04;
  if (slider.hRef > slider.wRef / 5) slider.hRef = slider.wRef / 5;

  slider.orgx = (maxx - slider.wRef) / 2;
  slider.orgy = maxy - 2 * slider.hRef;

// markers

  let x1Marker = slider.orgx;
  let x2Marker = slider.orgx + 0.3 * slider.wRef;
  let x3Marker = slider.orgx + 0.7 * slider.wRef;
  let x4Marker = slider.orgx + 1.0 * slider.wRef;

  let y1Marker = slider.orgy;
  let y2Marker = slider.orgy - slider.hRef;
  let y3Marker = slider.orgy + slider.hRef;

  slider.radiusMarker = 0.4 * slider.hRef;
  slider.radiusMarkerSelected = 0.6 * slider.hRef;
  slider.markerPos = [[x1Marker, y1Marker],
                    [x2Marker, y1Marker],
                    [x3Marker, y2Marker],
                    [x4Marker, y2Marker],
                    [x4Marker, y3Marker],
                   ];

  let path1 = `M ${x1Marker} ${y1Marker} L ${slider.orgx + 0.4 * slider.wRef} ${y1Marker}`;
  path1 += ` C ${slider.orgx + 0.5 * slider.wRef} ${y1Marker} ${slider.orgx + 0.5 * slider.wRef} ${y2Marker} ${slider.orgx + 0.6 * slider.wRef} ${y2Marker} L ${x4Marker} ${y2Marker}`;
  path1 += ` M ${slider.orgx + 0.4 * slider.wRef} ${y1Marker}`;
  path1 += ` C ${slider.orgx + 0.5 * slider.wRef} ${y1Marker} ${slider.orgx + 0.5 * slider.wRef} ${y3Marker} ${slider.orgx + 0.6 * slider.wRef} ${y3Marker} L ${x4Marker} ${y3Marker}`;

  slider.slider.setAttribute('stroke','#444');
  slider.slider.setAttribute('stroke-width', 0.2 * slider.hRef);
  slider.slider.setAttribute('stroke-linecap', 'round');
  slider.slider.setAttribute('d',path1);
  slider.slider.setAttribute('fill','none');

  slider.marker.forEach((marker, k) => {
    marker.setAttribute ('cx', slider.markerPos[k][0]);
    marker.setAttribute ('cy', slider.markerPos[k][1]);
    marker.setAttribute ('r', slider.radiusMarker);
    marker.setAttribute ('fill','#ccc');
    marker.setAttribute ('class', 'marker');
  }); // slider.marker.forEach

// background

  slider.bg.setAttribute ('x', x1Marker - 0.1 * slider.wRef);
  slider.bg.setAttribute ('y', y2Marker - 0.7 * slider.hRef);
  slider.bg.setAttribute ('width', 1.2 * slider.wRef);
  slider.bg.setAttribute ('height', 3.4 * slider.hRef);
  slider.bg.setAttribute ('fill','rgba(0, 0, 0, 0.6');
  slider.bg.setAttribute ('rx', 0.5 * slider.hRef);
  slider.bg.setAttribute ('ry', 0.5 * slider.hRef);

// comment area
  comment.style.width = 1.2 * slider.wRef + 'px';
  comment.style.left = x1Marker - 0.1 * slider.wRef + 'px';
  comment.style.fontSize = '16px';
  comment.style.height = '66px';
  comment.style.top = (y2Marker - 0.7 * slider.hRef - 76) + 'px';

} // dimensionSlider

//-----------------------------------------------------------------------------

    function createOneElement (type) {
      let elem = document.createElementNS(NS, type)
      svg.appendChild(elem);
      return elem;
    } // createOneElement

//-----------------------------------------------------------------------------

  function createSlider() {

    slider = {} ;
    slider.bg = createOneElement('rect');
    slider.slider = createOneElement('path');

    slider.marker = [];
    for (let k = 0; k < 5; ++k) {
      slider.marker[k] = createOneElement('circle');
      slider.marker[k].addEventListener('mousemove',((kk) => {return ()=> mouseMove(kk)})(k));

    }
  } // function createSlider

//-----------------------------------------------------------------------------
function completeTriangle (kv, ku, constraint, arIndices) {
/* returns false (nothing to do) if no new 'o' constraint is added
returns side (or diagonal) number if new 'o' constraint
  meaning new polygon to be created (if not at grid limits)*/

  let c = [constraint[arIndices[0]],
           constraint[arIndices[1]],
           constraint[arIndices[2]]];

  let idxo = c.indexOf('o');
  if (idxo != -1) { // one side 'opened': coerce others to 'closed'
    constraint[arIndices[0]] = constraint[arIndices[0]] || 'c';
    constraint[arIndices[1]] = constraint[arIndices[1]] || 'c';
    constraint[arIndices[2]] = constraint[arIndices[2]] || 'c';
    return false; // no new polygon
  }
/* no side constrained to 'opened' yet. We want one. Make a list of
unconstrained sides */

  let freeSides = [];
  if (! constraint[arIndices[0]]) freeSides.push(arIndices[0]);
  if (! constraint[arIndices[1]]) freeSides.push(arIndices[1]);
  if (! constraint[arIndices[2]]) freeSides.push(arIndices[2]);
  let chosen = randomElement(freeSides);
  constraint[arIndices[0]] = 'c';
  constraint[arIndices[1]] = 'c';
  constraint[arIndices[2]] = 'c';
  constraint[chosen] = 'o';
/*
  switch (chosen) {
    case 0 : addParallelogram(kv + 1, ku, 0); break;
    case 1 : addParallelogram(kv, ku + 1, 2); break;
    case 2 : addParallelogram(kv, ku, 0); break;
    case 3 : addParallelogram(kv, ku, 2); break;
    case 4 : addParallelogram(kv, ku, 1); break;
  }
*/
  return chosen;
} // completeTriangle

//-----------------------------------------------------------------------------
function screenCoordinates(p) {
  return [orgx + p[0] * v[0] + p[1] * u[0],
          orgy + p[0] * v[1] + p[1] * u[1]];
} // screenCoordinates

//-----------------------------------------------------------------------------
function createArrayOfTriangles () {

/* first create unit vectors
*/
  let alpha, beta, delta;

  let lRef = mmin(maxx, maxy) / 10; // about ten triangles in shortest direction x or y
// u
  let len = lRef * alea(0.9, 1.1);
  let th = mPI / 2 + alea(-0.1, 0.1); // to have u[0] >= 0
  u = [len * mcos(th), len * msin(th)];
// v
  len = lRef * alea(0.9, 1.1);
  th = mPI / 6 + alea(-0.2, 0.2);
  v = [len * mcos(th), len * msin(th)];

/* how many are needed ? */

  if (u[0] >= 0) {
    let delta = u[0] * v[1] - u[1] * v[0];
    nbu = (- maxx * v[1] - maxy * v[0]) / delta;
    nbv = (- maxy * u[0] - maxx * u[1]) / delta;
    orgx = - nbu * u[0];
    orgy = - nbv * v[1];
  } else {
    delta = u[1] * v[0] - u[0] * v[1];
    alpha = maxx * v[1] / delta;
    beta = maxx * u[1] / delta;
    orgx = - alpha * u[0];
    orgy = - alpha * u[1];
    let c0 = maxy - orgy + alpha * u[1];
    let c1 = maxx - orgx + alpha * u[0];
    nbu = (c0 * v[0] - c1 * v[1]) / delta;
    nbv = (u[1] * c1 - u[0] * c0) / delta;
  }
  nbu = mceil(nbu);
  nbv = mceil(nbv);

/* add a full row all around else we could get opened triangles in the screen */

  nbu += 2;
  nbv += 2;
  orgx -= (u[0] + v[0]);
  orgy -= (u[1] + v[1]);

/* the grid is made of squares in the u-v space.
  Each square is divided into 2 triangles by a SW-NE diagonal
  then we shall remove one side of each triangle, consistently with its neighbours
  So, every triangle in the original grid MUST keep two sides closed ('c' constraint)
and one opened ('o' constraint). The constraint are propagated from one triangle
to its neighbour through the shared side.
Sides of squares are numbered : 0: E, 1: S, 2: W, 3: N, 4: diagonal
  Remark : an open or closed side may be considered to be of one color or another.
  In this application, depending on the animation state, the sides will really be
  of two colors, or only one, or erased
*/

  let line, linePre;
  grid = [];
  linePre = []; // no preceeding line

/* First, create array of vertices.
  Thoses vertices will be used in addParallelogram so that parallelograms vertices
  with same coordinates do share the same javascript array instead of having two
  different arrays with same coordinates
*/
  vertices = [];
  for (let ku = 0; ku <= nbu; ++ku) {
    line = [];
    for (let kv = 0; kv <= nbv; ++kv) {
      line[kv] = screenCoordinates([kv, ku]);
    } // for kv
    vertices[ku] = line;
  }

  for (let ku = 0; ku < nbu; ++ku) {
    line = [];
    for (let kv = 0; kv < nbv; ++kv) {
      line[kv] = []; // no constraints a priori
// NW triangle
      if (kv > 0) line[kv][2] = line[kv - 1][0]
      if (ku > 0) if (linePre[kv]) line[kv][3] = linePre[kv][1];
      if (ku > 0 && (kv < nbv - 1 ) && linePre[kv + 1] && (linePre[kv + 1][1] == 'o')) {
        line[kv][0] = 'c'; // force 0 closed if upper right neighbour's 1 is open
      }
      completeTriangle(kv, ku, line[kv], [3, 4, 2]);
// SE triangle
      completeTriangle(kv, ku, line[kv], [0, 1, 4]);
//      draw (line[kv], kv, ku);
    } // for kv
    linePre = line;
    grid[ku] = line;
  } // for ku

//  create lines and triangles arrays

  lines = [];
  parallelograms = [];
  let l1, l2, l3;
  for (let ku = 0 ; ku < nbu ; ++ ku) {
    for (let kv = 0 ; kv < nbv ; ++ kv) {
      let p0 = vertices[ku][kv];
      let p1 = vertices[ku][kv + 1];
      let p2 = vertices[ku + 1][kv];
      let p3 = vertices[ku + 1][kv + 1];
      let cat = grid[ku][kv];
      lines.push (l1 = oneLine (p0, p1, cat[3]));
      lines.push (l2 = oneLine (p1, p2, cat[4]));
      lines.push (l3 = oneLine (p2, p0, cat[2]));
      /* create paralelograms */
      parallelograms.push (oneParallelogram(ku, kv));
// pick one triangle for an example
      if (!picked) {
        if ((p0[1] > 10) && (p0[0] <= maxx / 2) && (p1[0] >= maxx / 2)) {
          picked = [l1, l2, l3]; // memorize 3 sides
        }
      } // if !picked
    } // for kv
  } // for ku
} // createArrayOfTriangles

//-----------------------------------------------------------------------------
function oneLine (p0, p1, attrib) {
    let p = createOneElement('path');
    let d = `M ${p0[0]} ${p0[1]} L ${p1[0]} ${p1[1]}`;
    p.setAttribute('d', d);
    p.setAttribute('stroke-width', 2);
    p.setAttribute('stroke', lineOff);
    p.setAttribute('class','line');
    return {line : p, cat: attrib};
};

//-----------------------------------------------------------------------------
function oneParallelogram(ku, kv) {

    let p = createOneElement('polygon');
    let [x, y] = vertices[ku][kv];
    let cat = grid[ku][kv];
    let attrib;

    let d = `${x} ${y}`;
    if (cat[3] == 'o') {
      addPoint (-1, 1);
      addPoint (0, 1);
      addPoint (1, 0);
      attrib = 0;
    }
    if (cat[2] == 'o') {
      addPoint (0, 1);
      addPoint (1, 0);
      addPoint (1, -1);
      attrib = 1;
    }
    if (cat[4] == 'o') {
      addPoint (0, 1);
      addPoint (1, 1);
      addPoint (1, 0);
      attrib = 2;
    }

    p.setAttribute('points', d);
    p.setAttribute('stroke-width', 1);
    p.setAttribute('class','parallelogram');
    return {parallelogram: p, cat: attrib};

  function addPoint(nbu, nbv) {
    d += ` ${x + nbu * u[0] + nbv * v[0]} ${y + nbu * u[1] + nbv * v[1]}`;
  }
};

//-----------------------------------------------------------------------------

  function startOver() {
    maxx = window.innerWidth;
    maxy = window.innerHeight;

    if (maxx < 100 || maxy < 100) return false;
    if (maxx < 600) maxx = 600;

    svg.setAttribute("width", `${maxx}`);
    svg.setAttribute("height", `${maxy}`);

    createArrayOfTriangles();

    createSlider();
    dimensionSlider();

    selected = 1;
    requested = 0;
    request = true;

    return true;
  }

//-----------------------------------------------------------------------------
function mouseMove(k) {
  requested = k;
  request = true;
}

//-----------------------------------------------------------------------------
function setComment(txt) {
  comment.innerHTML = txt;
}

//-----------------------------------------------------------------------------
function displayLines(attr1, attr2) {
  lines.forEach (line => {
    line.line.setAttribute('stroke', (line.cat =='o') ? attr1 : attr2);
  });
}
//-----------------------------------------------------------------------------
function displayPicked() {
  picked.forEach (line => {
    line.line.setAttribute('stroke', (line.cat =='o') ? lineBlue : lineRed);
  });
}
//-----------------------------------------------------------------------------
function hideParallelograms() {
  parallelograms.forEach (parallelogram => {
    parallelogram.parallelogram.setAttribute('fill', 'rgba(0, 0, 0, 0)');
    parallelogram.parallelogram.setAttribute('stroke', 'rgba(0, 0, 0, 0)');
  });
}
//-----------------------------------------------------------------------------
function showParallelograms() {
  parallelograms.forEach (parallelogram => {
    let col = ['#444','#888','#ccc'][parallelogram.cat];
    parallelogram.parallelogram.setAttribute('fill', col);
    parallelogram.parallelogram.setAttribute('stroke', col);
  });
}
//-----------------------------------------------------------------------------
function enterState0() {
  hideParallelograms();
  displayLines(lineOff, lineOff);
  displayPicked();
};
//-----------------------------------------------------------------------------
function enterState1() {
  hideParallelograms();
  displayLines(lineBlue, lineRed);
};
//-----------------------------------------------------------------------------
function enterState2() {
  hideParallelograms();
  displayLines(lineOff, lineRed);
};
//-----------------------------------------------------------------------------
function enterState3() {
  showParallelograms();
  displayLines(lineOff, lineOff);
};
//-----------------------------------------------------------------------------
function enterState4() {
  hideParallelograms();
  displayLines(lineBlue, lineOff);
};
//-----------------------------------------------------------------------------

function animate() {

  let marker, r, color;
  let authorized =
            [[false, true, false, false, false],
             [true, false, true, false, true],
             [true, true, false, true, false],
             [true, true, true, false, false],
             [true, true, false, false, false]];

  switch (animState) {
    case 0:
      if (! startOver()) break; // cant't start - window too little ?
      animState = 1;
      break;

    case 1:
      if (! request) break;
      request = false;
      if (! authorized[selected][requested]) break;
      for (let k = 0; k < 5; ++k) {
        marker = slider.marker[k];
        if ( k == requested) {
          color = '#ccc';
          r = slider.radiusMarkerSelected;
        } else {
          color = authorized[requested][k] ? '#4c4' : '#c44';
          r = slider.radiusMarker;
        }
        marker.setAttribute ('r', r);
        marker.setAttribute ('fill', color);
      } // for k
      selected = requested;
      switch (selected) {
        case 0: setComment('Take a triangle with one blue side and two red ones.');
                enterState0();
                break;
        case 1: setComment('Tile the screen with it, matching the colors.');
                enterState1();
                break;
        case 2: setComment('Erase all the blue sides. You obtain parallelograms of three kinds.');
                enterState2();
                break;
        case 3: setComment('Colour the parallelograms with 3 colors according to their orientation.<br> You are (almost) the king of the 3D.');
                enterState3();
                break;
        case 4: setComment('Erase all the red sides. You may obtain interesting patterns.');
                enterState4();
                break;
      }
      break;
  }
    window.requestAnimationFrame(animate);
} // animate

//-----------------------------------------------------------------------------

  svg = document.createElementNS(NS,'svg');
  document.body.appendChild(svg);

  comment = document.getElementById('comment');

  animState = 0;
  animate();

}); // window load listener

              
            
!
999px

Console