live - 60fps 1920 x 1080
<!DOCTYPE html>
<html>
<head>
<title>Voronoi Shatter</title>
<style>
:root {
--bg-color: #060612;
--shard-1: #6366f1;
--shard-2: #ec4899;
--shard-3: #22d3ee;
--crack-color: #05050a;
}
* { 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];
}
const pal = [
hex(css.getPropertyValue('--shard-1') || '#6366f1'),
hex(css.getPropertyValue('--shard-2') || '#ec4899'),
hex(css.getPropertyValue('--shard-3') || '#22d3ee')
];
const crack = hex(css.getPropertyValue('--crack-color') || '#05050a');
const SCALE = 5;
const SEEDS = 18;
let lw, lh, img, buf, seeds;
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;
seeds = [];
for (let i = 0; i < SEEDS; i++) {
const c = pal[i % pal.length];
seeds.push({
x: Math.random() * lw, y: Math.random() * lh,
vx: (Math.random() - 0.5) * 0.35, vy: (Math.random() - 0.5) * 0.35,
c
});
}
}
addEventListener('resize', build);
build();
function step() {
for (const s of seeds) {
s.x += s.vx; s.y += s.vy;
if (s.x < 0 || s.x > lw) s.vx *= -1;
if (s.y < 0 || s.y > lh) s.vy *= -1;
}
for (let y = 0; y < lh; y++) {
for (let x = 0; x < lw; x++) {
let d1 = 1e9, d2 = 1e9, best = seeds[0];
for (const s of seeds) {
const dx = x - s.x, dy = y - s.y, d = dx * dx + dy * dy;
if (d < d1) { d2 = d1; d1 = d; best = s; }
else if (d < d2) { d2 = d; }
}
const edge = Math.sqrt(d2) - Math.sqrt(d1); // small near cell boundary
const i = (y * lw + x) * 4;
if (edge < 1.1) {
buf[i] = crack[0]; buf[i + 1] = crack[1]; buf[i + 2] = crack[2];
} else {
// subtle radial shading inside each shard for a glassy look
const shade = 0.55 + 0.45 * Math.max(0, 1 - Math.sqrt(d1) / 42);
buf[i] = best.c[0] * shade; buf[i + 1] = best.c[1] * shade; buf[i + 2] = best.c[2] * shade;
}
buf[i + 3] = 255;
}
}
ctx.putImageData(img, 0, 0);
}
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
step();
} else {
(function loop() { step(); requestAnimationFrame(loop); })();
}
</script>
</body>
</html> About this animation
Glowing stained-glass cells drift and reshape like slowly cracking ice, each glassy shard shaded from its core with dark fracture lines between.
Related