SOURCE

console 命令行工具 X clear

                    
>
console
"use strict";

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

  const SPEEDANIM = 1;
  const MIN_WIDTH = 50;
  const MAX_WIDTH = 100;

  let canv, ctx;    // canvas and context

  let maxx, maxy;   // canvas dimensions
  let width;
  let nbx, nby;
  let rects;
  let vs, hs;
  let tbHuesSat, tbColors, numColor;
  let comment;

// for animation
  let tStampRef;   // time stamp ref for animation
  let events;

// 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 mPIS3 = Math.PI / 3;
  const m2PI = Math.PI * 2;
  const m2PIS3 = Math.PI * 2 / 3;
  const msin = Math.sin;
  const mcos = Math.cos;
  const matan2 = Math.atan2;

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

  const rac3   = msqrt(3);
  const rac3s2 = rac3 / 2;

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

function alea (mini, maxi) {
// random number in given range

  if (typeof(maxi) == 'undefined') return mini * mrandom(); // range 0..mini

  return mini + mrandom() * (maxi - mini); // range mini..maxi
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function intAlea (mini, maxi) {
// random integer in given range (mini..maxi - 1 or 0..mini - 1)
//
  if (typeof(maxi) == 'undefined') return mfloor(mini * mrandom()); // range 0..mini - 1
  return mini + mfloor(mrandom() * (maxi - mini)); // range mini .. maxi - 1
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  function removeElement(array, element) {
    let idx = array.indexOf(element);
    if (idx == -1) throw ('Bug ! indexOf -1 in removeElement');
    array.splice(idx, 1);
  } // removeElement

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  function randomElement(array) {
    return array[intAlea(0, array.length)];
  }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  function arrayShuffle (array) {
/* randomly changes the order of items in an array
   only the order is modified, not the elements
*/
  let k1, temp;
  for (let k = array.length - 1; k >= 1; --k) {
    k1 = intAlea(0, k + 1);
    temp = array[k];
    array[k] = array[k1];
    array[k1] = temp;
    } // for k
  return array
  } // arrayShuffle

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

/* returns intermediate point between p0 and p1,
  alpha = 0 whill preturn p0, alpha = 1 will return p1
  values of alpha outside [0,1] may be used to compute points outside the p0-p1 segment
*/
  function intermediate (p0, p1, alpha) {

    return [(1 - alpha) * p0[0] + alpha * p1[0],
            (1 - alpha) * p0[1] + alpha * p1[1]];
  } // function intermediate

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

function Rect (kx, ky, horiz) {
  this.kx = kx;
  this.ky = ky;
  this.horiz = horiz;

  if (this.horiz) {
    this.grps = [[hs[ky][kx], hs[ky][kx + 1]],
                 [vs[ky][kx + 2]],
                 [hs[ky + 1][kx + 1], hs[ky + 1][kx]],
                 [vs[ky][kx]]];
  } else {
    this.grps = [[hs[ky][kx]],
                 [vs[ky][kx + 1], vs[ky + 1][kx + 1]],
                 [hs[ky + 2][kx]],
                 [vs[ky + 1][kx], vs[ky][kx]]];
  }

  this.grps.forEach (grp => {
    grp.forEach (side => {
      if (!side.tile1) side.tile1 = this;
      else side.tile2 = this;
    }); // grp.forEach
  }); // this.grps.forEach
} // Rect

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

Rect.prototype.contour = function(lineWidth, color) {
  let x = this.kx * width;
  let y = this.ky * width;
  ctx.beginPath()
  if (this.horiz) {
    ctx.rect(x, y, 2 * width, width);
  } else {
    ctx.rect(x, y, width, 2 * width);
  }
  ctx.lineWidth = lineWidth;
  ctx.strokeStyle = color;
  ctx.stroke();
}

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

Rect.prototype.inside = function(lineWidth, color) {
  let x = this.kx * width;
  let y = this.ky * width;
  ctx.beginPath()
  ctx.moveTo (x, y);
  ctx.lineTo (x + width / 2, y + width / 2);
  if (this.horiz) {
    ctx.lineTo (x + 3 * width / 2, y + width / 2);
    ctx.lineTo (x + 2 * width, y);
    ctx.moveTo (x, y + width);
    ctx.lineTo (x + width / 2, y + width / 2);
    ctx.moveTo (x + 3 * width / 2, y + width / 2);
    ctx.lineTo (x + 2 * width, y + width);

  } else {
    ctx.lineTo (x + width / 2, y + 3 * width / 2 );
    ctx.lineTo (x, y + 2 * width);
    ctx.moveTo (x + width, y);
    ctx.lineTo (x + width / 2, y + width / 2);
    ctx.moveTo (x + width / 2, y + 3 * width / 2);
    ctx.lineTo (x + width, y + 2 * width);

  }
  ctx.lineWidth = lineWidth;
  ctx.strokeStyle = color;
  ctx.stroke();
}

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

Rect.prototype.drawAlpha = function(alpha) {
  let x = this.kx * width;
  let y = this.ky * width;
  let alphaWidth = width * alpha / 2;
  let malphaWidth = width * (1 - alpha) / 2;

  if (this.horiz) {
    ctx.beginPath();
    ctx.moveTo (x, y + alphaWidth );
    ctx.lineTo (x + malphaWidth, y + width / 2);
    ctx.lineTo (x, y + width - alphaWidth);
    ctx.strokeStyle = tbColors[this.grps[3][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x + alphaWidth, y);
    ctx.lineTo (x + (2 + alpha) * width / 4, y + width / 2 - alphaWidth / 2);
    ctx.lineTo (x + 2 * width - (2 + alpha) * width / 4, y + width / 2 - alphaWidth / 2);
    ctx.lineTo (x + 2 * width - alphaWidth, y);
    ctx.strokeStyle = tbColors[this.grps[0][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x + width - alphaWidth, y);
    ctx.lineTo (x + width - alphaWidth / 2, y + alphaWidth / 2);
    ctx.lineTo (x + width + alphaWidth / 2, y + alphaWidth / 2);
    ctx.lineTo (x + width + alphaWidth, y);
    ctx.strokeStyle = tbColors[this.grps[0][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x + 2 * width, y + alphaWidth);
    ctx.lineTo (x + 1.5 * width + alphaWidth, y + width / 2);
    ctx.lineTo (x + 2 * width , y + width - alphaWidth);
    ctx.strokeStyle = tbColors[this.grps[1][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x + alphaWidth, y + width);
    ctx.lineTo (x + (2 + alpha) * width / 4, y + width / 2 + alphaWidth / 2);
    ctx.lineTo (x + 2 * width - (2 + alpha) * width / 4, y + width / 2 + alphaWidth / 2);
    ctx.lineTo (x + 2 * width - alphaWidth, y + width);
    ctx.strokeStyle = tbColors[this.grps[2][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x + width - alphaWidth, y + width);
    ctx.lineTo (x + width - alphaWidth / 2, y + width - alphaWidth / 2);
    ctx.lineTo (x + width + alphaWidth / 2, y + width - alphaWidth / 2);
    ctx.lineTo (x + width + alphaWidth, y + width);
    ctx.strokeStyle = tbColors[this.grps[2][0].color];
    ctx.stroke();

  } else {

    ctx.beginPath();
    ctx.moveTo (x + alphaWidth , y);
    ctx.lineTo (x + width /2, y + malphaWidth);
    ctx.lineTo (x + width - alphaWidth, y);
    ctx.strokeStyle = tbColors[this.grps[0][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x + width, y + alphaWidth);
    ctx.lineTo (x + width / 2 + alphaWidth / 2, y + (2 + alpha) * width / 4);
    ctx.lineTo (x + width / 2 + alphaWidth / 2, y + 2 * width - (2 + alpha) * width / 4);
    ctx.lineTo (x + width, y + 2 * width - alphaWidth);
    ctx.strokeStyle = tbColors[this.grps[1][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x + width, y + width - alphaWidth);
    ctx.lineTo (x + width - alphaWidth / 2, y + width - alphaWidth / 2);
    ctx.lineTo (x + width - alphaWidth / 2, y + width + alphaWidth / 2);
    ctx.lineTo (x + width, y + width + alphaWidth);
    ctx.strokeStyle = tbColors[this.grps[1][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x + alphaWidth , y + 2 * width);
    ctx.lineTo (x + width /2, y + 2 * width - malphaWidth);
    ctx.lineTo (x + width - alphaWidth, y + 2 * width);
    ctx.strokeStyle = tbColors[this.grps[2][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x, y + alphaWidth);
    ctx.lineTo (x + width / 2 - alphaWidth / 2, y + (2 + alpha) * width / 4);
    ctx.lineTo (x + width / 2 - alphaWidth / 2, y + 2 * width - (2 + alpha) * width / 4);
    ctx.lineTo (x, y + 2 * width - alphaWidth);
    ctx.strokeStyle = tbColors[this.grps[3][0].color];
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo (x, y + width - alphaWidth);
    ctx.lineTo (x + alphaWidth / 2, y + width - alphaWidth / 2);
    ctx.lineTo (x + alphaWidth / 2, y + width + alphaWidth / 2);
    ctx.lineTo (x, y + width + alphaWidth);
    ctx.strokeStyle = tbColors[this.grps[3][0].color];
    ctx.stroke();

  }
} // Rect.prototype.drawAlpha

//------------------------------------------------------------------------
function createRects() {

// prepare arrays of (empty) objects for vertical and horizontal sides
  vs = Array(nby + 1).fill(0).map(() => Array(nbx + 2).fill(0).map(() => ({})));
  hs = Array(nby + 2).fill(0).map(() => Array(nbx + 1).fill(0).map(() => ({})));

  let grid = [];

  let choices, choice;

  rects = [];
  grid[0] = []; // occupied squares (each rect occupies 2 squares)
  for (let ky = 0; ky < nby; ++ky) {
    grid[ky + 1] = [];
    for (let kx = 0; kx < nbx; ++kx) {
      if (grid[ky][kx]) continue; // already occupied
      choices = [false]; // choice only vertical
      if (! grid[ky][kx + 1]) choices.push(true); // right cell empty, add horizontal choice
      choice = randomElement(choices);
      rects.push(new Rect(kx, ky, choice));
      grid[ky][kx] = true; // this cell occupied
      if (choice) grid[ky][kx + 1] = true;
      else grid[ky + 1][kx] = true;
    } // for kx
  } // for ky

} // createRects

//------------------------------------------------------------------------
function colorizeSides (){

  let grps;
  let side;
  numColor = 0; // color counter
  let lstPropag;
  let elem, cellp, grpp, kgrpp, cell2;

  rects.forEach(rect => {
    rect.grps.forEach ((grp, kgrp) => {
// find an uncolored side
      side = grp.find(sd => sd.color === undefined);
      if (side === undefined) return; // all sides already colorized
      lstPropag = [{cell: rect, grp: grp, kgrp: kgrp}];
      ++numColor;
      while (lstPropag.length) {
        elem = lstPropag.pop(); //
        cellp = elem.cell;
        grpp = elem.grp;
        kgrpp = elem.kgrp;
        grpp.forEach((side, kside) => {
          if (side.color){
            if (side.color != numColor) throw ('mélange de couleurs !!!!!!!!!!!!');
            return;
          }
          side.color = numColor;
       // push neighbour (if any)
          cell2 = undefined;
          if (side.tile1 == cellp) {
            if (side.tile2) cell2 = side.tile2
          } else cell2 = side.tile1;
          if (! cell2) return; // no neighbour on this side
          lstPropag.push({cell: cell2, grp: cell2.grps[kgrpp ^ 2], kgrp: kgrpp ^ 2 });
        }); // grpp.forEach
      } // while (lstPropag.length)
    }); // grps.forEach
  }); // rects.forEach

} // colorizeSides
//------------------------------------------------------------------------

let animate;

{ // scope for animate

let animState = 0;
let alpha, dalpha;
let oneRect;
let firstRun = true;

animate = function(tStamp) {

  let event, tinit;

  event = events.pop();
  if (event && event.event == 'reset') animState = 0;
  if (event && event.event == 'click' && animState >= 10) animState = 0;
  window.requestAnimationFrame(animate)

  tinit = performance.now();
  do {

    switch (animState) {

      case 0:
        if (startOver()) {
          ++animState;
        }
        break;

      case 1:
        if (firstRun) {
          setComment('Make a 2x1 rectangle from 2 squares.<br>Click to continue.');
  // find an horizontal rectangle close to the center
          let minerr = 10000;
          let err;
          rects.forEach (rect => {
            if (!rect.horiz) return;
            err = 2 * mabs(rect.kx - mfloor(nbx / 2)) + mabs(rect.ky - mfloor(nby / 2));
            if (err < minerr) { // better candidate
              minerr = err;
              oneRect = rect;
            }
          });
          oneRect.contour(3,'#888');
          alpha = 0;
          dalpha = 0.07;
          firstRun = false;
          ++animState;
        } else {
          setComment('');
          animState = 10;
        }
        break;

      case 2:
        clearDisplay();
        ctx.moveTo((oneRect.kx + 1) * width, oneRect.ky * width);
        ctx.lineTo((oneRect.kx + 1) * width, (oneRect.ky + 1) * width);
        ctx.lineWidth = 3;
        ctx.strokeStyle = `rgba(136,136,136,${alpha}`;
        ctx.stroke();
        alpha += dalpha;
        if (alpha > 1) {
          alpha = 1;
          dalpha = - mabs(dalpha);
        }
        if (alpha < 0) {
          alpha = 0;
          dalpha = mabs(dalpha);
        }
        oneRect.contour(3,'#888');

        if (event && event.event == 'click') ++animState; // wait
        break;

      case 3:
        clearDisplay();
        oneRect.contour(3,'#888');
        setComment('Fill the display with this rectangle, putting it horizontally or vertically.');
        alpha = 0;
        ++animState;
        break;

      case 4:
        rects[alpha].contour(3,'#888');
        ++alpha;
        if (alpha >= rects.length) {
          setComment('Fill the display with this rectangle, putting it horizontally or vertically.<br>Click to continue.');
          ++animState;
        }
        break;

      case 5:
        if (event && event.event == 'click') {
          ++animState;
          setComment('Draw double Y-shaped lines in each rectangle.');
          alpha = 0;
        }
        break;

      case 6:
        rects[alpha].inside(3,'#fc8');
        ++alpha;
        if (alpha >= rects.length) {
          setComment('Draw double Y-shaped lines in each rectangle.<br>Click to continue.');
          ++animState;
        }
        break;

      case 7:
        if (event && event.event == 'click') {
          ++animState;
          setComment('Delete the rectangles themselves.');
          alpha = 1;
          dalpha = 0.02;
        }
        break;

      case 8:
        clearDisplay();
        rects.forEach(rect => rect.contour(3,`rgba(128,128,128,${alpha}`));
        rects.forEach(rect => rect.inside(3,'#fc8'));
        alpha -= dalpha;
        if (alpha < 0) {
          ++animState;
          setComment('Delete the rectangles themselves.<br>Click to continue.');
          clearDisplay();
          rects.forEach(rect => rect.inside(3,'#fc8'));
        }
        break;


      case 9:
        if (event && event.event == 'click') {
          ++animState;
          setComment('Fill the resulting shapes as you like.<br>Click for new pattern.');
        }
        break;

      case 10:
          ctx.lineWidth = 1;
          alpha = 0;
          dalpha = 1.5 / width;
          ++animState;

      case 11:
        tbColors = [];
        for (let k = 1; k <= numColor; ++k) tbColors[k] = `hsl(${tbHuesSat[k]},${30 + 50 * alpha}%)`;

        rects.forEach(rect => {
          rect.drawAlpha(alpha);
        }); // rects.forEach
        alpha += dalpha;
        if (alpha > 1) {
          ++animState;
          alpha = 1;
        }
        // wait
        break;

      case 12:
        tbColors = [];
        for (let k = 1; k <= numColor; ++k) tbColors[k] = `hsl(${tbHuesSat[k]},${30 + 50 * alpha}%)`;

        rects.forEach(rect => {
          rect.drawAlpha(alpha);
        }); // rects.forEach
        alpha -= dalpha;
        if (alpha < 0) {
          ++animState; // stop
        }
        // wait
        break;

    } // switch
  } while ((animState == 6) && (performance.now() - tinit < SPEEDANIM));

} // animate
} // scope for animate

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

function startOver() {

// canvas dimensions

  maxx = window.innerWidth;
  maxy = window.innerHeight;

  canv.width = maxx;
  canv.height = maxy;
  ctx.lineJoin = 'bevel';
  ctx.lineCap = 'round';

  width = intAlea(MIN_WIDTH, MAX_WIDTH + 1);

  nbx = mceil(maxx / width);
  nby = mceil(maxy / width);

  if (nbx < 3 || nby < 3) return false;

  createRects();

  colorizeSides();

  tbHuesSat = [];
  for (let k = 1; k <= numColor; ++k) tbHuesSat[k] = `${intAlea(360)}, ${intAlea(50,101)}%`;

// comment area
  comment.style.width = `${0.8 * maxx}px`;
  comment.style.left = `${0.1 * maxy}px`;
  comment.style.fontSize = '16px';
  comment.style.height = '66px';
  comment.style.top = `${maxy - 68}px`;

  return true;

} // startOver
//-----------------------------------------------------------------------------

function clearDisplay() {
  ctx.fillStyle = '#fff';
  ctx.fillRect(0,0,maxx,maxy);
}

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

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

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

function mouseClick (event) {

  events.push({event:'click'});;

} // mouseClick

//------------------------------------------------------------------------
//------------------------------------------------------------------------
// beginning of execution

  {
    canv = document.createElement('canvas');
    canv.style.position="absolute";
    document.body.appendChild(canv);
    ctx = canv.getContext('2d');
    canv.setAttribute ('title','click me');
  } // création CANVAS
  window.addEventListener('click',mouseClick); // just for initial position
  events = [{event:'reset'}];
  requestAnimationFrame (animate);
  comment = document.getElementById ('comment');

}); // window load listener
<div id = comment>
Hi, How are you ?
</div>
body {
  font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
  background-color: #000;
  margin:0;
  padding:0;
  border-width:0;
  cursor: pointer;
}
#comment {
  position: absolute;
  background-color: rgba(0, 0, 0, 0.0);
  color: black;
  border-radius: 10%;
  text-align: center;
  z-index: 10;
}