Created
May 13, 2024 12:46
-
-
Save ksamuel/2f02c479a1ae9fe01db7f7741e079c23 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// the HTML Video element | |
const bgVideo = document.getElementById('bgVideo'); | |
// The canvas element on which we display the animation | |
const canvas = document.getElementById('spaceCanvas'); | |
const ctx = canvas.getContext('2d'); | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
// I create a second canvas for performance reasons (explained later) | |
const offscreenCanvas = document.createElement('canvas'); | |
offscreenCanvas.width = canvas.width; | |
offscreenCanvas.height = canvas.height; | |
const offscreenCtx = offscreenCanvas.getContext('2d'); | |
// You'll note bellow the totally scientific constants and magic numbers | |
// that I had in no way brute forced until it looked good | |
const numStars = 1000; | |
let stars = []; | |
let speed = 2; | |
const mid_wd = canvas.width / 2; | |
const mid_ht = canvas.height / 2; | |
const width = canvas.width; | |
const height = canvas.height; | |
// Each star is a dot in space with a tail and the length depends of | |
// the speed so we store previous positions which we keep drawing. | |
class Star { | |
constructor() { | |
this.x = (Math.random() - 0.5) * width; | |
this.y = (Math.random() - 0.5) * height; | |
this.z = Math.random() * width; // Faking depth | |
this.prevX = 0; | |
this.prevY = 0; | |
this.prevPositions = []; | |
} | |
update() { | |
this.z -= speed; | |
const prevX = this.x * (width / this.z) + mid_wd; | |
const prevY = this.y * (width / this.z) + mid_ht; | |
this.prevPositions.unshift({ x: prevX, y: prevY }); | |
const threshold = 50; | |
if (this.z <= threshold) { | |
this.prevPositions = []; // remove the tail | |
} | |
if (this.z <= 0) { | |
this.x = (Math.random() - 0.5) * width; | |
this.y = (Math.random() - 0.5) * height; | |
this.z = width; | |
} | |
// Limit the number of previous positions based on speed | |
const maxPrevPositions = Math.floor(speed * 5); | |
while (this.prevPositions.length > maxPrevPositions) { | |
this.prevPositions.pop(); | |
} | |
} | |
draw() { | |
const scale = width / this.z; | |
const x = this.x * scale + mid_wd; | |
const y = this.y * scale + mid_ht; | |
if (speed > 2 && this.prevPositions.length > 1) { | |
// We draw all the positions on the canvas | |
const furthestPos = this.prevPositions[this.prevPositions.length - 1]; | |
ctx.strokeStyle = 'white'; | |
ctx.lineWidth = scale / 4; | |
ctx.beginPath(); | |
ctx.moveTo(furthestPos.x, furthestPos.y); | |
ctx.lineTo(x, y); | |
ctx.stroke(); | |
} else { | |
// just one dot | |
ctx.fillStyle = 'white'; | |
ctx.beginPath(); | |
ctx.arc(x, y, scale, 0, Math.PI * 2); | |
ctx.fill(); | |
} | |
} | |
} | |
// Initialize stars. Say it aloud with a robot voice. | |
for (let i = 0; i < numStars; i++) { | |
stars.push(new Star()); | |
} | |
function animate() { | |
// Clear offscreen canvas | |
ctx.drawImage(bgVideo, 0, 0, canvas.width, canvas.height); | |
// Optimization trick: each star is drawned first on | |
// an offscreen canvas to avoid rerendering everytime. | |
// When all of them are updated, we copy | |
// the result on the main canvas. | |
for (let star of stars) { | |
star.update(); | |
star.draw(offscreenCtx); | |
} | |
// Draw the offscreen canvas onto the main canvas | |
ctx.drawImage(offscreenCanvas, 0, 0); | |
requestAnimationFrame(animate); | |
} | |
animate() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment