Animated Triangles on Canvas JS HTML
Cool looking triangles animation using just canvas and js.
Explanation of Code
-
The code starts with the statement
'use strict';
which enables strict mode, a way to introduce better error-checking and prevent the use of certain error-prone features in JavaScript. -
The code sets up an event listener for the
load
event on thewindow
object. This event fires when the entire page and its associated resources have finished loading. -
There are several variables declared using the
let
keyword, such astriWidth
,triHeight
,firstRun
,canv
,ctx
,maxx
,maxy
,lRef
,grid
,nbx
,nby
,hnbx
,hnby
,blocks
,nbLines
, andevents
. These variables will be used throughout the code to store and manipulate various values. -
Following the variable declarations, there are a series of constants defined using the
const
keyword. These constants represent mathematical values and functions such asmrandom
,mfloor
,mround
,mceil
,mabs
,mmin
,mmax
,mPI
,mPIS2
,m2PI
,msin
,mcos
,matan2
,mhypot
,msqrt
,rac3
,rac3s2
, andmPIS3
. These constants provide shortcuts for commonly used mathematical operations. -
The code then defines a function called
alea(mini, maxi)
which generates a random number within a given range. -
Next, there is a function called
intAlea(mini, maxi)
which generates a random integer within a given range. -
Another function called
arrayShuffle(array)
is defined, which shuffles the order of items in an array. -
The code includes a function called
lerp(p0, p1, alpha)
which calculates a linear interpolation point between two points based on a given alpha value. -
The
addP(text)
function creates a new<p>
element and appends it to an element with the idtxtexplain
. -
The code defines a class called
Triangle
, which represents a triangle in the grid. It includes various methods for setting up and manipulating triangles. -
There is a function called
createGrid()
which creates a grid of triangles. -
The code defines a function called
createBlock(tri, side)
which creates a block of connected triangles based on a starting triangle and a side. -
The
createBlocks()
function creates blocks of triangles in the grid. -
The
orientBlock(block)
function orients the triangles in a block based on their entry and exit points. -
Lastly, there are functions
drawBlock(block, alpha, hue, light, widthCoeff)
andhatchaab(alpha, hue, lineWidth, lum)
which are responsible for drawing and rendering the triangles and blocks on a canvas.
Animated Triangles on Canvas JS HTML Demo
View Demo Full Screen View Demo New Tab
Animated Triangles on Canvas JS HTML Code
HTML
<div id=explain>
<div id="txtexplain"></div>
<p class=buttline><button type="button" id="butt2">next >></button> <button type="button" id="butt3">skip it
all</button></p>
</div>
CSS
body {
font-family: Arial, Helvetica, "Liberation Sans", FreeSans, sans-serif;
background-color: #000;
margin: 0;
padding: 0;
border-width: 0;
}
#explain {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10px;
background-color: rgba(255, 255, 255, 0.9);
border: 1px solid black;
border-radius: 10px;
padding: 10px;
color: black;
z-index: 1;
}
.hidden {
display: none;
}
#explain p.buttline {
text-align: center;
}
Javascript
"use strict";
window.addEventListener("load", function () {
let triWidth, triHeight; // length of triangle side and altitude
let firstRun = !location.pathname.includes("/fullcpgrid/");
let canv, ctx; // canvas and context
let maxx, maxy, lRef; // canvas dimensions (lRef is average)
let grid;
let nbx, nby;
let hnbx, hnby; // number of triangles in the half of the width, height of the canvas
let blocks, nbLines;
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 m2PI = Math.PI * 2;
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;
const mPIS3 = Math.PI / 3;
//------------------------------------------------------------------------
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 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 lerp point between p0 and p1,
alpha = 0 will return p0, alpha = 1 will return p1
values of alpha outside [0,1] may be used to compute points outside the p0-p1 segment
*/
function lerp(p0, p1, alpha) {
return {
x: (1 - alpha) * p0.x + alpha * p1.x,
y: (1 - alpha) * p0.y + alpha * p1.y
};
} // function lerp
//------------------------------------------------------------------------
function addP(text) {
let p = document.createElement("p");
p.append(text);
txtexplain.append(p);
}
//------------------------------------------------------------------------
class Triangle {
/* numbering of vertices / edges
0 2---1---1
/ \ \ /
2 0 2 0
/ \ \ /
2---1---1 0
*/
constructor(kx, ky) {
this.kx = kx;
this.ky = ky;
this.kxc = kx - hnbx;
this.kyc = ky - hnby;
this.upsideDown = (this.kxc + this.kyc) & 1; // 0 or 1
this.setXY();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setXY() {
let xa, ya, vertices, deltay, upsideDown;
// centre of this triangle (middle of height, not gravity centre)
this.ya = ya = maxy / 2 + this.kyc * triHeight;
this.xa = xa = maxx / 2 + (this.kxc * triWidth) / 2;
this.vertices = vertices = [];
deltay = triHeight / 2;
if (this.upsideDown) deltay = -deltay;
vertices[0] = { x: xa, y: ya - deltay };
vertices[1] = { x: xa + triWidth / 2, y: ya + deltay };
vertices[2] = { x: xa - triWidth / 2, y: ya + deltay };
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setBase(side) {
// where side is 0,1,2
this.base = side; // which side is the base ?
this.dirBase = [
[0, 1, 2],
[2, 1, 0]
][this.upsideDown][this.base]; // orientation of base (== base side number for normally oriented triangle)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setNeighbors() {
/* to be called only after the whole grid has been created */
let kx, ky;
this.neighbors = [];
for (let k = 0; k < 3; ++k) {
kx = this.kx + [1, 0, -1][k];
ky =
this.ky +
[
[0, 1, 0],
[0, -1, 0]
][this.upsideDown][k];
this.neighbors[k] =
kx < 0 || kx >= nbx || ky < 0 || ky >= nby ? false : grid[ky][kx];
} // for k
} // Triangle.setNeighbor
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setOneaab(side, value) {
// sets value of this.aab[side] and the matching neighbor (if any) too
// sets the "base" property to this side if value is 1
this.aab[side] = value;
if (this.neighbors[side]) {
this.neighbors[side].aab[2 - side] = value;
}
if (value == 1) {
this.setBase(side);
if (this.neighbors[side]) {
this.neighbors[side].setBase(2 - side);
}
}
}
setaab() {
/* sets 3 values of this.aab, taking into account the already filled values and the constraints relative to the neighborhood
*/
let neigh, possible, choice;
const zeroes = [];
const ones = [];
const empty = [];
// count 1s and 0s already present
for (let k = 0; k < 3; ++k) {
if (this.aab[k] === 0) zeroes.push(k);
else if (this.aab[k] === 1) ones.push(k);
else empty.push(k);
}
if (empty.length == 0) {
// already completed
if (ones.length != 1) throw "wtf ???";
return; // already completed, ok
}
if (ones.length > 1) throw "wtf ???";
if (ones.length == 1) {
// already a 1, no choice
empty.forEach((s) => this.setOneaab(s, 0));
return;
}
// no "1" already present. Check empty edges to check if 1 are possible
possible = [];
empty.forEach((s) => {
neigh = this.neighbors[s];
if (!neigh || !neigh.aab.includes(1)) possible.push(s);
});
if (possible.length == 0) throw "impossible to add a 1";
choice = possible[intAlea(possible.length)];
this.setOneaab(choice, 1);
empty.splice(empty.indexOf(choice), 1);
// fill the rest with 0
while (empty.length > 0) {
choice = empty.pop();
this.setOneaab(choice, 0);
}
} // setaab
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
drawaab(lineWidth, opacity) {
ctx.beginPath();
this.aab.forEach((v, k) => {
ctx.beginPath();
ctx.moveTo(this.vertices[k].x, this.vertices[k].y);
ctx.lineTo(this.vertices[(k + 1) % 3].x, this.vertices[(k + 1) % 3].y);
ctx.lineWidth = lineWidth;
ctx.strokeStyle = `hsla(${[120, 0][v]},100%,50%,${opacity})`;
ctx.stroke();
});
} // draw aab
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
hatchaab(alpha, hue, lineWidth, lum = 50) {
// for alpha = 0..1
const pa = lerp(this.vertices[this.a], this.vertices[this.b], alpha);
const pb = lerp(this.vertices[this.c], this.vertices[this.b], alpha);
ctx.beginPath();
ctx.moveTo(pa.x, pa.y);
ctx.lineTo(pb.x, pb.y);
ctx.lineWidth = lineWidth;
ctx.strokeStyle = `hsl(${hue},100%,${lum}%)`;
ctx.stroke();
}
} // class Triangle
//------------------------------------------------------------------------
function createGrid() {
let kx1, ky1, cell;
let stackDist = [];
grid = [];
for (let ky = 0; ky < nby; ++ky) {
grid[ky] = [];
for (let kx = 0; kx < nbx; ++kx) {
grid[ky][kx] = new Triangle(kx, ky);
} // for kx
} // for ky
grid.forEach((line) => line.forEach((tri) => tri.setNeighbors()));
grid.forEach((line) => line.forEach((tri) => (tri.aab = [])));
grid.forEach((line) => line.forEach((tri) => tri.setaab()));
} // createGrid
//------------------------------------------------------------------------
function createBlock(tri, side) {
if (tri.block) return false; // already belongs to a block : forget
const initTri = tri;
let nextTri;
const block = [];
while (true) {
block.push(tri);
tri.entry = side;
tri.exit = tri.aab.findIndex((v, k) => k != tri.entry && v == 0);
tri.block = block;
/* fetch next triangle - if any */
nextTri = tri.neighbors[tri.exit];
if (nextTri === false) {
block.closed = false;
return block; // finished open path
}
if (nextTri === initTri) {
block.closed = true;
return block; // finished closed
}
side = 2 - tri.exit;
tri = nextTri;
} // while
} // createBlock
//------------------------------------------------------------------------
function createBlocks() {
blocks = [];
let block;
// create all opened paths (begining on edge)
grid.forEach((line) =>
line.forEach((tri) => {
if (tri.block) return; // already belongs to a block
let ext = [];
tri.neighbors.forEach((v, k) => {
if (v === false && tri.aab[k] == 0) ext.push(k);
});
if (ext.length == 0) return; // not on external side, ignore
block = createBlock(tri, ext[intAlea(ext.length)]);
if (block) blocks.push(block);
})
);
// create all closed paths
grid.forEach((line) =>
line.forEach((tri) => {
if (tri.block) return; // already belongs to a block
block = createBlock(
tri,
tri.aab.findIndex((v) => v === 0)
);
if (block) blocks.push(block);
})
);
blocks.forEach(orientBlock);
blocks.forEach((block) => (block.hue = intAlea(360)));
}
//------------------------------------------------------------------------
function orientBlock(block) {
block.forEach((tri, k) => {
if ((tri.entry + 1) % 3 == tri.base) {
tri.a = (tri.entry + 1) % 3; // base point of entry
tri.b = tri.entry; // summit
tri.c = (tri.entry + 2) % 3; // base point of exit
} else {
tri.a = tri.entry; // base point of entry
tri.b = (tri.entry + 1) % 3; // summit
tri.c = (tri.entry + 2) % 3; // base point of exit
}
let ntri = block[(k + 1) % block.length];
tri.invertAlpha = ntri.dirBase == tri.dirBase;
}); // block
} // orientBlock
//------------------------------------------------------------------------
function drawBlock(block, alpha, hue, light, widthCoeff = 1) {
const tri = block[0];
let pint0;
let pa = lerp(tri.vertices[tri.a], tri.vertices[tri.b], alpha);
let pts = [pa];
let pint;
block.forEach((tri) => {
pts.push(lerp(tri.vertices[tri.c], tri.vertices[tri.b], alpha));
if (tri.invertAlpha) alpha = 1 - alpha;
});
ctx.beginPath();
pts.forEach((p, k) => {
if (k == 0) {
pint0 = pint = lerp(p, pts[1], 0.5);
if (block.closed) {
ctx.moveTo(pint.x, pint.y);
} else {
ctx.moveTo(pa.x, pa.y);
ctx.lineTo(pint.x, pint.y);
}
} else if (k == pts.length - 1) {
if (block.closed) {
ctx.quadraticCurveTo(pa.x, pa.y, pint0.x, pint0.y);
ctx.closePath();
} else ctx.lineTo(p.x, p.y);
} else {
pint = lerp(p, pts[k + 1], 0.5);
ctx.quadraticCurveTo(p.x, p.y, pint.x, pint.y);
}
});
const lw = 3 + (triHeight / nbLines - 3) * widthCoeff; // (width 3 for coeff 0)
for (let l = lw; l > 0; --l) {
ctx.lineWidth = l;
ctx.strokeStyle = `hsl(${hue},100%,${20 + (light - 20) * (1 - l / lw)}%)`;
ctx.stroke();
}
} // drawBlock
//------------------------------------------------------------------------
//------------------------------------------------------------------------
function drawBlockRounded(block, alpha, hue, light, round) {
/* similiar to drawBlock, the round parameter determines how corners are rounded, from 0(angular) to 1 (totally rounded)
and no glossy effect is applied
*/
const tri = block[0];
let pint0;
let pa = lerp(tri.vertices[tri.a], tri.vertices[tri.b], alpha);
let pts = [pa];
let pinta, pintb;
block.forEach((tri) => {
pts.push(lerp(tri.vertices[tri.c], tri.vertices[tri.b], alpha));
if (tri.invertAlpha) alpha = 1 - alpha;
});
ctx.beginPath();
pts.forEach((p, k) => {
if (k == 0) {
pint0 = pinta = lerp(p, pts[1], 0.5 * round);
pintb = lerp(p, pts[1], 1 - 0.5 * round);
if (block.closed) {
ctx.moveTo(pinta.x, pinta.y);
} else {
ctx.moveTo(pa.x, pa.y);
}
ctx.lineTo(pintb.x, pintb.y);
} else if (k == pts.length - 1) {
if (block.closed) {
ctx.quadraticCurveTo(pa.x, pa.y, pint0.x, pint0.y);
ctx.closePath();
} else ctx.lineTo(p.x, p.y);
} else {
pinta = lerp(p, pts[k + 1], 0.5 * round);
pintb = lerp(p, pts[k + 1], 1 - 0.5 * round);
ctx.quadraticCurveTo(p.x, p.y, pinta.x, pinta.y);
ctx.lineTo(pintb.x, pintb.y);
}
});
ctx.lineWidth = 3;
ctx.strokeStyle = `hsl(${hue},100%,${light}%)`;
ctx.stroke();
} // drawBlockRounded
//------------------------------------------------------------------------
function* animateTri1() {
const drawn = []; // already drawn triangles
let tInit,
dt,
tri,
current,
rot,
cCurr,
cTarget,
angle,
duration,
speed,
kx,
ky;
txtexplain.innerHTML = "";
addP("Tile a canvas with equilateral triangles.");
addP(
"These triangles must have 2 green edges and one red, and colors must match. It's a bit tricky."
);
ky = 0;
kx = 0;
for (let nb = 1; ; ++nb) {
// let's animate tiles
speed = mmin(nb * 0.3,3);
//if (speed > 5) break;
tInit = performance.now();
// pick triangle
tri = grid[ky][kx];
cTarget = {};
cTarget.x = tri.vertices.reduce((s, p) => s + p.x, 0) / 3;
cTarget.y = tri.vertices.reduce((s, p) => s + p.y, 0) / 3;
// create a fake triangle (for initial position and animation)
current = new Triangle(hnbx, hnby);
current.upsideDown = 0;
current.setXY();
current.aab = [0, 1, 0];
cCurr = {};
cCurr.x = current.vertices.reduce((s, p) => s + p.x, 0) / 3;
cCurr.y = current.vertices.reduce((s, p) => s + p.y, 0) / 3;
duration = mhypot(cTarget.x - cCurr.x, cTarget.y - cCurr.y) / speed;
// rotation required to go from current to tri
rot =
([
[-2, 0, 2],
[-1, 3, 1]
][tri.upsideDown][tri.base] *
mPI) /
3;
while (true) {
dt = mmin(duration, performance.now() - tInit);
angle = (rot * dt) / duration - mPI / 2;
let c = lerp(cCurr, cTarget, dt / duration);
current.vertices[0].x = c.x + (triWidth / 2) * mcos(angle);
current.vertices[0].y = c.y + (triWidth / 2) * msin(angle);
current.vertices[1].x =
c.x + (triWidth / 2) * mcos(angle + (2 * mPI) / 3);
current.vertices[1].y =
c.y + (triWidth / 2) * msin(angle + (2 * mPI) / 3);
current.vertices[2].x =
c.x + (triWidth / 2) * mcos(angle - (2 * mPI) / 3);
current.vertices[2].y =
c.y + (triWidth / 2) * msin(angle - (2 * mPI) / 3);
// clear screen and draw triangles : drawn and current
ctx.clearRect(0, 0, maxx, maxy);
drawn.forEach((tri) => tri.drawaab(2, 1));
current.drawaab(3, 1);
yield;
if (dt == duration) break; // exit "while" when done
} // while
drawn.push(tri);
// pick next triangle
if (ky == 0 || kx + 1 >= grid[0].length) {
ky = kx + ky + 1;
kx = 0;
while (ky >= grid.length) {
// should not happen
--ky;
++kx;
}
if (kx >= grid[0].length) break; // exit "for", no more triangles to put
} else {
--ky;
++kx;
}
}
ctx.clearRect(0, 0, maxx, maxy);
grid.forEach((line) => line.forEach((tri) => tri.drawaab(2, 1)));
} // animateTri1
//------------------------------------------------------------------------
function* animateTri2() {
txtexplain.innerHTML = "";
addP("Add each triangle hatch lines parallel to the red edge.");
addP(`Here we are using ${nbLines} hatch lines.`);
ctx.clearRect(0, 0, maxx, maxy);
grid.forEach((line) => line.forEach((tri) => tri.drawaab(2, 1)));
let kx, ky, tri;
ky = 0;
kx = 0;
let tInit = performance.now();
for (let nb = 1; ; ++nb) {
// let's animate a few tiles
tri = grid[ky][kx];
for (let k = 0; k < nbLines; ++k) {
tri.hatchaab((k + 0.5) / nbLines, 48, 2);
yield;
} // for k
// pick next triangle
if (ky == 0 || kx + 1 >= grid[0].length) {
ky = kx + ky + 1;
kx = 0;
while (ky >= grid.length) {
--ky;
++kx;
}
if (kx >= grid[0].length) break; // done all , exit for
} else {
--ky;
++kx;
}
} // for nb;
ctx.clearRect(0, 0, maxx, maxy);
grid.forEach((line) => line.forEach((tri) => tri.drawaab(2, 1)));
grid.forEach((line) =>
line.forEach((tri) => {
for (let k = 0; k < nbLines; ++k)
tri.hatchaab((k + 0.5) / nbLines, 48, 2);
})
);
} // animateTri2
//------------------------------------------------------------------------
function* animateTri3() {
txtexplain.innerHTML = "";
addP("Delete the triangles.");
let tInit = performance.now();
const duration = 3000;
while (true) {
let lum = mmax(0, 1 - (performance.now() - tInit) / duration);
ctx.clearRect(0, 0, maxx, maxy);
grid.forEach((line) => line.forEach((tri) => tri.drawaab(2, lum)));
grid.forEach((line) =>
line.forEach((tri) => {
for (let k = 0; k < nbLines; ++k)
tri.hatchaab((k + 0.5) / nbLines, 48, 2);
})
);
yield;
if (lum == 0) break;
}
ctx.clearRect(0, 0, maxx, maxy);
grid.forEach((line) =>
line.forEach((tri) => {
for (let k = 0; k < nbLines; ++k)
tri.hatchaab((k + 0.5) / nbLines, 48, 2);
})
);
} // animateTri3
//------------------------------------------------------------------------
function* animateTri4() {
txtexplain.innerHTML = "";
addP(
"Connect segments togetether to actually form broken lines, or polygons if lines are closed."
);
ctx.clearRect(0, 0, maxx, maxy);
grid.forEach((line) =>
line.forEach((tri) => {
for (let k = 0; k < nbLines; ++k)
tri.hatchaab((k + 0.5) / nbLines, 48, 1.5);
})
);
let nblocks = arrayShuffle(blocks.slice()); // shuffled list of blocks
while (nblocks.length) {
let block = nblocks.pop();
for (let kLine = 0; kLine < nbLines; ++kLine) {
let alpha = (kLine + 0.5) / nbLines;
let lum = 30 + 50 * (kLine / (nbLines - 1));
for (let kTri = 0; kTri < block.length; ++kTri) {
block[kTri].hatchaab(alpha, block.hue, 3, lum);
yield;
if (block[kTri].invertAlpha) alpha = 1 - alpha;
} // for kTri
} // for kLine
} // while nblock.length
} // animateTri4
//------------------------------------------------------------------------
function* animateTri5() {
let tInit, round;
const duration = 3000;
txtexplain.innerHTML = "";
addP("Soften all these sharp angles.");
tInit = performance.now();
do {
round = mmin(1, (performance.now() - tInit) / duration);
ctx.clearRect(0, 0, maxx, maxy);
blocks.forEach((block) => {
for (let k = 0; k < nbLines; ++k) {
drawBlockRounded(
block,
(k + 0.5) / nbLines,
block.hue,
30 + 50 * (k / (nbLines - 1)),
round
);
} // for k
}); // blocks. forEach
yield;
} while (round < 1);
} // animateTri5
//------------------------------------------------------------------------
function* animateTri6() {
let tInit, widthk;
const duration = 3000;
txtexplain.innerHTML = "";
addP("Add some fun.");
butt3.classList.add("hidden");
butt2.innerHTML = "end";
tInit = performance.now();
do {
widthk = mmin(1, (performance.now() - tInit) / duration);
ctx.clearRect(0, 0, maxx, maxy);
blocks.forEach((block) => {
for (let k = 0; k < nbLines; ++k) {
drawBlock(
block,
(k + 0.5) / nbLines,
block.hue,
30 + 50 * (k / (nbLines - 1)),
widthk
);
} // for k
}); // blocks. forEach
yield;
} while (widthk < 1);
} // animateTri6
//------------------------------------------------------------------------
let animate;
{
// scope for animate
let animState;
let subAnim;
animate = function (tStamp) {
const event = events.pop();
let nxt;
window.requestAnimationFrame(animate);
if (event && event.event == "reset") animState = 0;
if (event && event.event == "click") animState = 0;
if (event && event.event == "butt3") {
firstRun = false;
animState = 1;
}
switch (animState) {
case 0:
if (startOver()) {
++animState;
}
break;
case 1:
animState++;
if (firstRun) animState = 10;
else explain.classList.add("hidden");
break;
case 2:
ctx.clearRect(0, 0, maxx, maxy);
blocks.forEach((block) => {
for (let k = 0; k < nbLines; ++k)
drawBlock(
block,
(k + 0.5) / nbLines,
block.hue,
30 + 50 * (k / (nbLines - 1))
);
});
++animState;
break;
case 10:
subAnim = animateTri1();
++animState;
case 11:
nxt = subAnim.next();
if (nxt.done) ++animState;
// break; no ! no break here
case 12:
if (event && event.event == "butt2") animState = 15;
break;
case 15:
subAnim = animateTri2();
++animState;
case 16:
nxt = subAnim.next();
if (nxt.done) ++animState;
case 17:
if (event && event.event == "butt2") animState = 20;
break;
case 20:
subAnim = animateTri3();
++animState;
case 21:
nxt = subAnim.next();
if (nxt.done) ++animState;
case 22:
if (event && event.event == "butt2") animState = 25;
break;
case 25:
subAnim = animateTri4();
++animState;
case 26:
nxt = subAnim.next();
if (nxt.done) ++animState;
case 27:
if (event && event.event == "butt2") animState = 30;
break;
case 30:
subAnim = animateTri5();
++animState;
case 31:
nxt = subAnim.next();
if (nxt.done) ++animState;
case 32:
if (event && event.event == "butt2") animState = 35;
break;
case 35:
subAnim = animateTri6();
firstRun = false;
++animState;
case 36:
nxt = subAnim.next();
if (nxt.done) ++animState;
case 37:
if (event && event.event == "butt2") animState = 1;
break;
} // switch
}; // animate
} // scope for animate
//------------------------------------------------------------------------
function startOver() {
// canvas dimensions
maxx = window.innerWidth;
maxy = window.innerHeight;
canv.width = maxx;
canv.height = maxy;
ctx.lineJoin = "bevel";
lRef = msqrt(maxx * maxy);
triWidth = (lRef / 12) * alea(0.8, 1.2);
triHeight = triWidth * rac3s2;
hnby = mceil((maxy / triHeight - 1) / 2); // the full array has 2 * hnbx + 1 rows
hnbx = mceil(maxx / triWidth);
// uncomment the next 2 lines to see what's going on the edges
// hnbx -= 2;
// hnby -= 1;
nbx = 1 + 2 * hnbx;
nby = 1 + 2 * hnby;
if (nbx < 3 || nby < 3) return false;
nbLines = intAlea(2, 6);
if (firstRun) {
nbLines = 3;
}
ctx.clearRect(0, 0, maxx, maxy);
createGrid();
createBlocks();
return true;
} // startOver
//------------------------------------------------------------------------
function mouseClick(event) {
events.push({ event: "click" });
} // mouseMove
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// beginning of execution
{
// document.body.style.backgroundColor = bgColor;
canv = document.createElement("canvas");
canv.style.position = "absolute";
document.body.appendChild(canv);
ctx = canv.getContext("2d");
} // création CANVAS
canv.addEventListener("click", mouseClick); // just for initial position
const explain = document.getElementById("explain");
const txtexplain = document.getElementById("txtexplain");
const butt2 = document.getElementById("butt2");
butt2.addEventListener("click", () => events.push({ event: "butt2" }));
const butt3 = document.getElementById("butt3");
butt3.addEventListener("click", () => events.push({ event: "butt3" }));
events = [{ event: "reset" }];
requestAnimationFrame(animate);
}); // window load listener
External Link for Animated Triangles on Canvas JS HTML