Skip to content

Instantly share code, notes, and snippets.

@ksamuel
Created May 13, 2024 12:46
Show Gist options
  • Save ksamuel/2f02c479a1ae9fe01db7f7741e079c23 to your computer and use it in GitHub Desktop.
Save ksamuel/2f02c479a1ae9fe01db7f7741e079c23 to your computer and use it in GitHub Desktop.
// 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