48h.arts.ens.fr/js/triangles-mask.js

276 lines
9.3 KiB
JavaScript
Raw Normal View History

(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 = 0.5;
const POINT_MAX_X_RADIUS = 2;
const POINT_MIN_Y_RADIUS = 0.5;
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 certain lines between all points to form contours of triangles
function drawTriangleContours () {
// Width of the contours
context.lineWidth = 3;
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]);
}
}
}
}
// Transform the triangle contours into plain white triangles
// by iterating over the pixels of the canvas image data
function fillTrianglesAndEmptyContours () {
const currentImageData = context.getImageData(0, 0, WIDTH, HEIGHT);
const currentData = currentImageData.data;
const newImageData = context.createImageData(WIDTH, HEIGHT);
const newData = newImageData.data;
const dataLength = currentData.length;
for (i = 0; i < dataLength; i += 4) {
newData[i + 0] = 255;
newData[i + 1] = 255;
newData[i + 2] = 255;
newData[i + 3] = 255 - currentData[i + 3];
}
context.putImageData(newImageData, 0, 0);
}
/*--------------------------------------------------------*/
/* 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;
}
// Clear the canvas and repeat the drawing process
context.clearRect(0, 0, WIDTH, HEIGHT);
drawTriangleContours();
fillTrianglesAndEmptyContours();
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);
})();