Silk Flow Field
Live previewPerformance varies by device
No account needed to copy or download. Check dependencies and thelicense status before publishing.
<!DOCTYPE html>
<html>
<head>
<title>Silk Flow Field</title>
<style>
:root {
--bg-color: #05060c;
--stream-1: #22d3ee;
--stream-2: #a855f7;
}
* { 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 = hex(css.getPropertyValue('--bg-color') || '#05060c');
const s1 = hex(css.getPropertyValue('--stream-1') || '#22d3ee');
const s2 = hex(css.getPropertyValue('--stream-2') || '#a855f7');
let w, h, parts;
const N = 1300;
const mouse = { x: -9999, y: -9999, active: false };
const rnd = (a, b) => a + Math.random() * (b - a);
function build() {
w = cv.width = innerWidth;
h = cv.height = innerHeight;
parts = [];
for (let i = 0; i < N; i++) parts.push({ x: Math.random() * w, y: Math.random() * h, life: rnd(0, 240) });
ctx.fillStyle = `rgb(${bg[0]},${bg[1]},${bg[2]})`;
ctx.fillRect(0, 0, w, h);
}
// Smooth curl-like flow field from layered trig — cheap, no noise tables
function angle(px, py, t) {
const s = 0.0015;
const a = Math.sin(px * s + t) * Math.cos(py * s * 1.3 - t * 0.6)
+ Math.sin((px + py) * s * 0.5 + t * 0.4);
return a * Math.PI * 1.4;
}
addEventListener('mousemove', e => { mouse.x = e.clientX; mouse.y = e.clientY; mouse.active = true; });
addEventListener('mouseleave', () => { mouse.active = false; });
addEventListener('resize', build);
build();
let t = 0;
function step() {
t += 0.002;
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle = `rgba(${bg[0]},${bg[1]},${bg[2]},0.045)`;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'lighter';
ctx.lineWidth = 2.2;
for (const p of parts) {
const a = angle(p.x, p.y, t);
let vx = Math.cos(a), vy = Math.sin(a);
if (mouse.active) {
const dx = p.x - mouse.x, dy = p.y - mouse.y, d = Math.hypot(dx, dy) || 1;
if (d < 190) { const f = (1 - d / 190); vx += (-dy / d) * f * 2.6; vy += (dx / d) * f * 2.6; }
}
const ox = p.x, oy = p.y;
p.x += vx * 1.5; p.y += vy * 1.5; p.life--;
const mix = Math.sin(a) * 0.5 + 0.5;
const r = s1[0] + (s2[0] - s1[0]) * mix | 0;
const g = s1[1] + (s2[1] - s1[1]) * mix | 0;
const b = s1[2] + (s2[2] - s1[2]) * mix | 0;
ctx.strokeStyle = `rgba(${r},${g},${b},1)`;
ctx.beginPath(); ctx.moveTo(ox, oy); ctx.lineTo(p.x, p.y); ctx.stroke();
if (p.x < 0 || p.x > w || p.y < 0 || p.y > h || p.life < 0) {
p.x = Math.random() * w; p.y = Math.random() * h; p.life = rnd(60, 260);
}
}
}
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
for (let i = 0; i < 320; i++) step(); // paint a static silk field, then hold
} else {
(function loop() { step(); requestAnimationFrame(loop); })();
}
</script>
</body>
</html>About this animation
Thousands of particles stream along a flowing vector field, weaving silky ribbons of light that swirl into a vortex around your cursor.
Under the hood
Trig Flow-Field Silk Streams
On
Canvas 2D, 1,300 particles follow a flow-field angle built from layered Math.sin() and Math.cos() terms, with no noise library or lookup table, each drawing a short 2.2px segment at full alpha per frame under globalCompositeOperation = 'lighter'. A translucent background fillRect() at alpha 0.045 fades old segments into silk-like strands, and particle colour mixes between the two stream colours by Math.sin() of the field angle. Within 190px of the mouse, particles get an added tangential swirl force perpendicular to the cursor direction.