live - 60fps 1920 x 1080
<!DOCTYPE html>
<html>
<head>
<title>ASCII Waves</title>
<style>
:root {
--bg-color: #050810;
--ink-1: #22d3ee;
--ink-2: #c084fc;
}
* { margin: 0; padding: 0; }
body { height: 100vh; background: var(--bg-color); overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv = document.getElementById('c');
const ctx = cv.getContext('2d');
const css = getComputedStyle(document.documentElement);
function hex(h) {
h = (h || '').trim().replace('#', '');
if (h.length === 3) h = h.split('').map(c => c + c).join('');
const n = parseInt(h, 16); return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
}
const bg = (css.getPropertyValue('--bg-color') || '#050810').trim();
const i1 = hex(css.getPropertyValue('--ink-1') || '#22d3ee');
const i2 = hex(css.getPropertyValue('--ink-2') || '#c084fc');
const CELL = 15;
const RAMP = ' .:-=+*#%@';
let w, h, cols, rows;
const mouse = { x: -9999, y: -9999 };
function build() {
w = cv.width = innerWidth; h = cv.height = innerHeight;
cols = Math.ceil(w / CELL); rows = Math.ceil(h / CELL);
ctx.font = `${CELL}px "JetBrains Mono", ui-monospace, monospace`;
ctx.textBaseline = 'top';
}
addEventListener('resize', build);
addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; });
addEventListener('mouseleave', () => { mouse.x = mouse.y = -9999; });
build();
function step(t) {
ctx.fillStyle = bg;
ctx.fillRect(0, 0, w, h);
for (let j = 0; j < rows; j++) {
for (let i = 0; i < cols; i++) {
const px = i * CELL, py = j * CELL;
let v = Math.sin(i * 0.28 + t) * 0.5
+ Math.sin(j * 0.34 - t * 0.8) * 0.5
+ Math.sin((i + j) * 0.18 + t * 0.6) * 0.6
+ Math.sin(Math.hypot(i - cols / 2, j - rows / 2) * 0.35 - t * 1.4) * 0.7;
// cursor ripple
const dx = px - mouse.x, dy = py - mouse.y, d = Math.hypot(dx, dy);
if (d < 220) v += (1 - d / 220) * 1.6;
let n = (v / 2.8) * 0.5 + 0.5;
n = Math.max(0, Math.min(0.999, n));
const ch = RAMP[(n * RAMP.length) | 0];
if (ch === ' ') continue;
const r = i1[0] + (i2[0] - i1[0]) * n | 0;
const g = i1[1] + (i2[1] - i1[1]) * n | 0;
const b = i1[2] + (i2[2] - i1[2]) * n | 0;
ctx.fillStyle = `rgba(${r},${g},${b},${0.25 + n * 0.75})`;
ctx.fillText(ch, px, py);
}
}
}
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
step(1.0);
} else {
let t = 0;
(function loop() { t += 0.05; step(t); requestAnimationFrame(loop); })();
}
</script>
</body>
</html> About this animation
A living field of ASCII characters rippling in waves, density shifting from faint dots to bold glyphs, with a ripple that blooms out from your cursor.
Related