(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); })();