e51a59b738
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.
260 lines
No EOL
8.7 KiB
JavaScript
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);
|
|
})(); |