48h.arts.ens.fr/js/triangles-mask.js
Daru13 e51a59b738 Implement a more performant drawing method.
Instead of drawing opaque triangle contours on an empty canvas and
iterating overt its pixels to invert the opacity, use the
globalCompositeOperation property of the canvas to fill the canvas with
white, and draw "transparent" triangle contours.
2019-04-23 02:20:15 +02:00

260 lines
No EOL
8.7 KiB
JavaScript

(function () {
/*--------------------------------------------------------*/
/* INITIALISATION
/*--------------------------------------------------------*/
const canvas = document.getElementById("main-header-background");
const context = canvas.getContext("2d");
// Get the node dimensions and enforce the canvas width and height
let WIDTH = canvas.clientWidth;
let HEIGHT = canvas.clientHeight;
canvas.setAttribute("width", WIDTH);
canvas.setAttribute("height", HEIGHT);
// Delay between point positions' updates
const POINT_POSITIONS_UPDATE_DELAY = 50; // ms
// Number of points and initial position parameters
const NB_POINTS_PER_ROW = 4 + Math.round(WIDTH / 200);
const NB_POINTS_PER_COL = 5;
const INIT_ROW_SPACING = HEIGHT / (NB_POINTS_PER_COL - 1);
const INIT_COL_SPACING = WIDTH / (NB_POINTS_PER_ROW - 3);
const X_ORIGIN = -INIT_COL_SPACING;
const Y_ORIGIN = -100;
const INIT_MAX_X_SHIFT = INIT_ROW_SPACING / 3;
const INIT_MAX_Y_SHIFT = INIT_COL_SPACING / 4;
// Movement of the points
const POINT_MIN_X_SPEED = 0.15;
const POINT_MAX_X_SPEED = 0.3;
const POINT_MIN_Y_SPEED = 0.15;
const POINT_MAX_Y_SPEED = 0.3;
const POINT_MIN_X_RADIUS = 1;
const POINT_MAX_X_RADIUS = 2;
const POINT_MIN_Y_RADIUS = 1;
const POINT_MAX_Y_RADIUS = 2;
// Canvas update halting
const NB_CANVAS_UPDATES_DELAYS_TO_RECORD = 100;
const ANIM_HALT_TIME_THRESHOLD = 100; // ms
/*--------------------------------------------------------*/
/* GRID OF POINTS
/*--------------------------------------------------------*/
const points = [];
// Create all points
// Each point is defined by its position
// plus several parameters which control its movement
function createAllPoints () {
for (let row = 0; row < NB_POINTS_PER_COL; row++) {
for (let col = 0; col < NB_POINTS_PER_ROW; col++) {
points.push({
// Initial position
x: X_ORIGIN + col * INIT_COL_SPACING + ((row % 2 === 1) ? INIT_COL_SPACING : 0),
y: Y_ORIGIN + row * INIT_ROW_SPACING,
// Parameters of the movement
xDirection: (Math.random() > 0.5 ? 1 : -1),
yDirection: (Math.random() > 0.5 ? 1 : -1),
xSpeed: (Math.random() * (POINT_MAX_X_SPEED - POINT_MIN_X_SPEED)) + POINT_MIN_X_SPEED,
ySpeed: (Math.random() * (POINT_MAX_Y_SPEED - POINT_MIN_Y_SPEED)) + POINT_MIN_Y_SPEED,
xRadius: (Math.random() * (POINT_MAX_X_RADIUS - POINT_MIN_X_RADIUS)) + POINT_MIN_X_RADIUS,
yRadius: (Math.random() * (POINT_MAX_Y_RADIUS - POINT_MIN_Y_RADIUS)) + POINT_MIN_Y_RADIUS
});
}
}
// Move each point a little bit
// This will make the triangular structure look more random
for (let point of points) {
point.x += (2 * Math.random() * INIT_MAX_X_SHIFT) - INIT_MAX_X_SHIFT;
point.y += (2 * Math.random() * INIT_MAX_Y_SHIFT) - INIT_MAX_Y_SHIFT;
}
}
createAllPoints();
/*--------------------------------------------------------*/
/* TRIANGLE DRAWING
/*--------------------------------------------------------*/
// Draw a line between two points
function drawLineBetweenPoints (from, to) {
context.beginPath();
context.moveTo(from.x, from.y);
context.lineTo(to.x, to.y);
context.stroke();
}
// Draw white triangles with transparent contours
function drawTriangles () {
// Style of the drawing
context.fillStyle = "#FFFFFF";
context.lineWidth = 3;
// Fill the canvas with a white background...
context.globalCompositeOperation = "source-over";
context.fillRect(0, 0, WIDTH, HEIGHT);
// ...and erase the contours of the triangles
context.globalCompositeOperation = "destination-out";
for (let row = 0; row < NB_POINTS_PER_COL; row++) {
for (let col = 0; col < NB_POINTS_PER_ROW; col++) {
// Nothing to do with the last point of each row
if (col == NB_POINTS_PER_ROW - 1) {
continue;
}
// Get the current index and point
const index = row * NB_POINTS_PER_ROW + col;
const point = points[index];
// When applicable, draw lines to
// - the next point on the same row
// - the aligned point on the previous row
// - the aligned point on the next row
if (! (row % 2 === 1 && col === NB_POINTS_PER_ROW - 2))
drawLineBetweenPoints(point, points[index + 1]);
if (row > 0)
drawLineBetweenPoints(point, points[index - NB_POINTS_PER_ROW]);
if (row < NB_POINTS_PER_COL - 1)
drawLineBetweenPoints(point, points[index + NB_POINTS_PER_ROW]);
// If the row number is odd, when applicable, also draw lines to
// - the next point on the previous row
// - the next point on the next row
if (row % 2 === 1) {
if (row > 0)
drawLineBetweenPoints(point, points[index - NB_POINTS_PER_ROW + 1]);
if (row < NB_POINTS_PER_COL - 1)
drawLineBetweenPoints(point, points[index + NB_POINTS_PER_ROW + 1]);
}
}
}
}
/*--------------------------------------------------------*/
/* POINT POSITIONS UPDATE
/*--------------------------------------------------------*/
// N° of the current position update iteration
let positionUpdateIter = 0;
function updatePointPositions () {
const nbPoints = points.length;
for (let i = 0; i < nbPoints; i++) {
const point = points[i];
point.x += point.xDirection * Math.cos(positionUpdateIter * point.xSpeed) * point.xRadius;
point.y += point.yDirection * Math.sin(positionUpdateIter * point.ySpeed) * point.yRadius;
}
positionUpdateIter++;
}
window.setInterval(updatePointPositions, POINT_POSITIONS_UPDATE_DELAY);
/*--------------------------------------------------------*/
/* CANVAS UPDATE
/*--------------------------------------------------------*/
// Array of times between canvas updates
// It is used to compute whether the drawing process should be halted
// (if the device is too slow for the animation to look fluid)
const canvasUpdateDelays = new Array(NB_CANVAS_UPDATES_DELAYS_TO_RECORD);
canvasUpdateDelays.fill(0);
// Last recorded time (it is set for the first time a bit later)
let lastRecordedTime = 0;
// Flag indicating whether the canvas update has been halted or not
let animationHasBeenHalted = false;
function resetHaltingMechanism () {
canvasUpdateDelays.fill(0);
lastRecordedTime = performance.now();
}
function recordTimeSinceLastCanvasUpdate (time) {
canvasUpdateDelays.shift();
canvasUpdateDelays.push(time - lastRecordedTime);
lastRecordedTime = time;
}
function animationShouldBeHalted () {
let sumOfTimes = canvasUpdateDelays.reduce((sumOfTimes, time) => {
return sumOfTimes + time;
}, 0);
// console.log(sumOfTimes / NB_CANVAS_UPDATES_DELAYS_TO_RECORD);
return (sumOfTimes / NB_CANVAS_UPDATES_DELAYS_TO_RECORD) > ANIM_HALT_TIME_THRESHOLD;
}
function updateCanvas (currentTime) {
// Record the time since last update, and halt the animation if required
recordTimeSinceLastCanvasUpdate(currentTime);
if (animationShouldBeHalted()) {
animationHasBeenHalted = true;
return;
}
// Repeat the drawing process
drawTriangles();
window.requestAnimationFrame(updateCanvas);
}
resetHaltingMechanism();
updateCanvas(lastRecordedTime);
/*
* If the page is hidden (e.g. on tab change), the web browser
* is likely to stop calling requestAnimationFrame (to save ressources).
* This may halt the animation when the user displays the page again,
* since the time since the last redraw will possibly be very high.
*
* Therefore, on each visibility change, the halting mechanism must be reset!
*/
document.addEventListener("visibilitychange", () => {
resetHaltingMechanism();
// In case the animation has been halted before this callback was executed,
// the canvas update must be restarted
if (animationHasBeenHalted)
updateCanvas(lastRecordedTime);
}, false);
})();