library / 3d & webgl / dna-helix
live - 60fps 1920 x 1080
dna-helix.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>DNA Helix</title>
<style>
    :root { --bg-color: #05070f; --strand-1: #22d3ee; --strand-2: #f472b6; }
    * { 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'), ctx = cv.getContext('2d');
    const css = getComputedStyle(document.documentElement);
    const bg = (css.getPropertyValue('--bg-color') || '#05070f').trim();
    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 s1 = hex(css.getPropertyValue('--strand-1') || '#22d3ee');
    const s2 = hex(css.getPropertyValue('--strand-2') || '#f472b6');
    let w, h, cx, R, count, spacing;
    function build() { w = cv.width = innerWidth; h = cv.height = innerHeight; cx = w / 2; R = Math.min(w * 0.17, 165); spacing = 9; count = Math.ceil(h / spacing) + 6; }
    addEventListener('resize', build); build();
    function bead(x, y, z, col) {
        const depth = (z + 1) / 2;              // 0 back .. 1 front
        const rad = 2.2 + depth * 5.5;
        const a = 0.18 + depth * 0.82;
        ctx.globalAlpha = a * 0.35; ctx.fillStyle = `rgb(${col[0]},${col[1]},${col[2]})`;
        ctx.beginPath(); ctx.arc(x, y, rad * 2.1, 0, Math.PI * 2); ctx.fill();      // soft glow
        ctx.globalAlpha = a; ctx.beginPath(); ctx.arc(x, y, rad, 0, Math.PI * 2); ctx.fill(); // core
    }
    function step(t) {
        ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);
        ctx.globalCompositeOperation = 'lighter';
        const scroll = t * 46;
        const items = [];
        for (let i = 0; i < count; i++) {
            const y = ((i * spacing + scroll) % (h + spacing * 6)) - spacing * 3;
            const ph = i * 0.34 + t * 1.6;
            const xA = cx + Math.sin(ph) * R, zA = Math.cos(ph);
            const xB = cx + Math.sin(ph + Math.PI) * R, zB = Math.cos(ph + Math.PI);
            if (i % 2 === 0) items.push({ t: 'r', y, xA, xB, z: Math.min(zA, zB) - 0.01 });
            items.push({ t: 'b', x: xA, y, z: zA, col: s1 });
            items.push({ t: 'b', x: xB, y, z: zB, col: s2 });
        }
        items.sort((a, b) => a.z - b.z);
        for (const it of items) {
            if (it.t === 'r') {
                ctx.globalAlpha = 0.12 + ((it.z + 1) / 2) * 0.28;
                ctx.strokeStyle = '#9fb4d8'; ctx.lineWidth = 2;
                ctx.beginPath(); ctx.moveTo(it.xA, it.y); ctx.lineTo(it.xB, it.y); ctx.stroke();
            } else bead(it.x, it.y, it.z, it.col);
        }
        ctx.globalCompositeOperation = 'source-over'; ctx.globalAlpha = 1;
    }
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) step(0.5);
    else { let t = 0; (function loop(){ t += 0.012; step(t); requestAnimationFrame(loop); })(); }
</script>
</body>
</html>

About this animation

A luminous double helix rotates and scrolls in 3D, cyan and magenta strands winding around glowing base-pair rungs.

Source Code
Copied to clipboard