live - 60fps 1920 x 1080
<!DOCTYPE html>
<html>
<head>
<title>Dither Drift</title>
<style>
:root {
--bg-color: #0b0f1a;
--tone-1: #0b0f1a;
--tone-2: #3b4d9c;
--tone-3: #38bdf8;
--tone-4: #f0abfc;
}
* { margin: 0; padding: 0; }
body { height: 100vh; background: var(--bg-color); overflow: hidden; }
canvas { display: block; width: 100vw; height: 100vh; image-rendering: pixelated; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv = document.getElementById('c');
const ctx = cv.getContext('2d');
ctx.imageSmoothingEnabled = false;
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];
}
// Ordered ramp of tones — dithering blends between adjacent stops
const pal = [
hex(css.getPropertyValue('--tone-1') || '#0b0f1a'),
hex(css.getPropertyValue('--tone-2') || '#3b4d9c'),
hex(css.getPropertyValue('--tone-3') || '#38bdf8'),
hex(css.getPropertyValue('--tone-4') || '#f0abfc')
];
// 8x8 Bayer ordered-dither matrix (normalized 0..1)
const B = [
0,48,12,60,3,51,15,63, 32,16,44,28,35,19,47,31,
8,56,4,52,11,59,7,55, 40,24,36,20,43,27,39,23,
2,50,14,62,1,49,13,61, 34,18,46,30,33,17,45,29,
10,58,6,54,9,57,5,53, 42,26,38,22,41,25,37,21
].map(v => (v + 0.5) / 64);
const SCALE = 4; // low-res factor -> chunky dither
let lw, lh, img, buf;
function build() {
cv.width = Math.ceil(innerWidth / SCALE);
cv.height = Math.ceil(innerHeight / SCALE);
lw = cv.width; lh = cv.height;
img = ctx.createImageData(lw, lh);
buf = img.data;
}
addEventListener('resize', build);
build();
function step(t) {
const levels = pal.length - 1;
for (let y = 0; y < lh; y++) {
for (let x = 0; x < lw; x++) {
const nx = x / lw, ny = y / lh;
// flowing plasma field in 0..1
let v = 0.5 + 0.5 * (
Math.sin(nx * 6 + t) * 0.5 +
Math.sin((nx + ny) * 5 - t * 0.8) * 0.3 +
Math.sin(Math.hypot(nx - 0.5, ny - 0.5) * 10 - t * 1.2) * 0.4
) / 1.2;
v = Math.max(0, Math.min(0.999, v));
const scaled = v * levels;
let lvl = Math.floor(scaled);
const frac = scaled - lvl;
// ordered dither between palette stop lvl and lvl+1
if (frac > B[(y & 7) * 8 + (x & 7)]) lvl++;
if (lvl > levels) lvl = levels;
const c = pal[lvl];
const i = (y * lw + x) * 4;
buf[i] = c[0]; buf[i + 1] = c[1]; buf[i + 2] = c[2]; buf[i + 3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
step(1.2);
} else {
let t = 0;
(function loop() { t += 0.02; step(t); requestAnimationFrame(loop); })();
}
</script>
</body>
</html> About this animation
A smooth plasma gradient rendered through an ordered Bayer dither into chunky retro pixels — the crunchy, nostalgic-yet-modern gradient look.
Related