SESSION — 02
Backgrounds
Starting with one background, adding more over time. Every one is a different experiment. The code and prompt are yours to take, copy, or break. Tag me on socials when you do something with it.
'use client'
// Background 01 — Animated grid with line tracers
import { useEffect, useRef } from 'react'
interface Tracer {
axis: 'h' | 'v'
linePos: number
rangeStart: number
rangeEnd: number
headPos: number
tailLength: number
speed: number
done: boolean
}
export default function Background01() {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')
if (!ctx) return
// Non-null locals so nested closures satisfy TypeScript
const c: HTMLCanvasElement = canvas
const cx: CanvasRenderingContext2D = ctx
const CELL = 32
const GRID_COLOR = '#efefef'
const TRACE_COLOR = '#b7b7b7'
const MAX_TRACERS = 14
const SPAWN_INTERVAL = 350 // ms
let tracers: Tracer[] = []
let animationId: number
let lastSpawn = 0
function resize() {
c.width = window.innerWidth
c.height = window.innerHeight
}
function spawnTracer() {
const cols = Math.floor(c.width / CELL)
const rows = Math.floor(c.height / CELL)
const axis = Math.random() > 0.5 ? 'h' : 'v'
if (axis === 'h') {
const row = Math.floor(Math.random() * (rows + 1))
const linePos = row * CELL
const cellLen = Math.floor(Math.random() * 5) + 3
const cellStart = Math.floor(Math.random() * Math.max(1, cols - cellLen))
const rangeStart = cellStart * CELL
const rangeEnd = Math.min((cellStart + cellLen) * CELL, c.width)
const tailLength = (rangeEnd - rangeStart) * 0.45
tracers.push({ axis, linePos, rangeStart, rangeEnd, headPos: rangeStart, tailLength, speed: 1.2 + Math.random() * 1.8, done: false })
} else {
const col = Math.floor(Math.random() * (cols + 1))
const linePos = col * CELL
const cellLen = Math.floor(Math.random() * 5) + 3
const cellStart = Math.floor(Math.random() * Math.max(1, rows - cellLen))
const rangeStart = cellStart * CELL
const rangeEnd = Math.min((cellStart + cellLen) * CELL, c.height)
const tailLength = (rangeEnd - rangeStart) * 0.45
tracers.push({ axis, linePos, rangeStart, rangeEnd, headPos: rangeStart, tailLength, speed: 1.2 + Math.random() * 1.8, done: false })
}
}
function drawGrid() {
cx.fillStyle = '#ffffff'
cx.fillRect(0, 0, c.width, c.height)
cx.strokeStyle = GRID_COLOR
cx.lineWidth = 1
for (let x = 0; x <= c.width; x += CELL) {
cx.beginPath()
cx.moveTo(x + 0.5, 0)
cx.lineTo(x + 0.5, c.height)
cx.stroke()
}
for (let y = 0; y <= c.height; y += CELL) {
cx.beginPath()
cx.moveTo(0, y + 0.5)
cx.lineTo(c.width, y + 0.5)
cx.stroke()
}
}
function updateAndDrawTracers() {
cx.strokeStyle = TRACE_COLOR
cx.lineWidth = 1
tracers = tracers.filter((t) => !t.done)
for (const t of tracers) {
t.headPos += t.speed
const tailPos = Math.max(t.rangeStart, t.headPos - t.tailLength)
if (tailPos >= t.rangeEnd) {
t.done = true
continue
}
const drawHead = Math.min(t.headPos, t.rangeEnd)
cx.beginPath()
if (t.axis === 'h') {
cx.moveTo(tailPos, t.linePos + 0.5)
cx.lineTo(drawHead, t.linePos + 0.5)
} else {
cx.moveTo(t.linePos + 0.5, tailPos)
cx.lineTo(t.linePos + 0.5, drawHead)
}
cx.stroke()
}
}
function animate(timestamp: number) {
drawGrid()
if (timestamp - lastSpawn > SPAWN_INTERVAL && tracers.length < MAX_TRACERS) {
spawnTracer()
lastSpawn = timestamp
}
updateAndDrawTracers()
animationId = requestAnimationFrame(animate)
}
resize()
window.addEventListener('resize', resize)
animationId = requestAnimationFrame(animate)
return () => {
cancelAnimationFrame(animationId)
window.removeEventListener('resize', resize)
}
}, [])
return <canvas ref={canvasRef} className="fixed inset-0 -z-10" />
}Background 01 — Animated Grid with Line Tracers
- ▪SESSION 03 — COMING SOON
- ▪SESSION 02 — BACKGROUNDS
- ▪SESSION 01 — 04.10.26