← Mike Higgins Home
Custom Code Experiment •

Chromatophores 4

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html>
<head>
    <style>
        html, body {
            overflow: hidden;
            background: #000;
            padding: 0px;
            margin: 0px;
            width: 100%;
            height: 100%;
        }
        canvas {
            display: block;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        var canvas = document.getElementById("canvas");
        var ctx = canvas.getContext("2d");

        var w = canvas.width = window.innerWidth;
        var h = canvas.height = window.innerHeight;

        window.onresize = function() {
            w = canvas.width = window.innerWidth;
            h = canvas.height = window.innerHeight;
        };

        var pixels = [];
        var emitterCount = 4;
        var maxPixels = 130;
        var pixelSize = 18;          // base size
        var gap = 6;
        var lineWidth = 2;
        var maxLifetime = 4500;
        var minLifetime = 2000;
        var spawnRate = 400;
        var opacity = 0.22;
        var hue = 0;
        var hueRange = 60;
        var fadeAlpha = 0.018;
        var shapes = ["pentagram"];   // only pentagrams

        function drawShape(shape, x, y, baseSize, pulse, sizeFactor) {
            var s = baseSize * pulse * sizeFactor;   // final size
            ctx.beginPath();
            if (shape === "pentagram") {
                var outerRadius = s / 2;
                var cx = x;
                var cy = y;
                var points = [];
                // 5 outer points, starting from top (-90°)
                for (var i = 0; i < 5; i++) {
                    var angle = -Math.PI/2 + i * (2 * Math.PI / 5);
                    points.push({
                        x: cx + outerRadius * Math.cos(angle),
                        y: cy + outerRadius * Math.sin(angle)
                    });
                }
                // Connect every second point to form a pentagram
                var order = [0, 2, 4, 1, 3, 0]; // closes back to start
                ctx.moveTo(points[order[0]].x, points[order[0]].y);
                for (var j = 1; j < order.length; j++) {
                    ctx.lineTo(points[order[j]].x, points[order[j]].y);
                }
            }
        }

        function emit() {
            for (var i = 0; i < emitterCount; i++) {
                if (pixels.length >= maxPixels) {
                    pixels.splice(0, 1);
                }
                var step = pixelSize + gap;
                pixels.push({
                    x: Math.ceil((Math.random() * w) / step) * step,
                    y: Math.ceil((Math.random() * h) / step) * step,
                    t: Math.random() * (maxLifetime - minLifetime) + minLifetime,
                    s: Date.now(),
                    h: Math.random() * ((hue + hueRange) - (hue - hueRange)) + (hue - hueRange),
                    shape: shapes[0], // always "pentagram"
                    pulseOffset: Math.random() * Math.PI * 2,
                    pulseSpeed: 2 + Math.random() * 3,
                    sizeFactor: 1 + Math.random() * 1.5  // range 1.0 to 2.5
                });
            }
        }

        function draw() {
            var now = Date.now();
            for (var i = 0; i < pixels.length; i++) {
                var p = pixels[i];
                var diff = (now - p.s) / p.t;
                var perc = Math.round(diff * 100);
                var pulse = 1 + 0.4 * Math.sin(p.pulseOffset + (now / 1000) * p.pulseSpeed);

                ctx.save();
                ctx.beginPath();
                ctx.globalAlpha = opacity;
                ctx.lineWidth = lineWidth;
                ctx.strokeStyle = "hsl(" + p.h + "," + perc + "%,55%)";
                ctx.fillStyle = "hsl(" + p.h + "," + perc + "%,55%)";

                drawShape(p.shape, p.x + pixelSize/2, p.y + pixelSize/2, pixelSize, pulse, p.sizeFactor);

                ctx.stroke();
                ctx.restore();
            }
        }

        function animate() {
            var step = pixelSize + gap;
            for (var i = 0; i < pixels.length; i++) {
                if (pixels[i].s + pixels[i].t < Date.now()) {
                    pixels.splice(i, 1);
                    i--;
                } else {
                    var dir = Math.round(Math.random() * 8) % 8;
                    switch(dir) {
                        case 0: pixels[i].y -= step; break;
                        case 1: pixels[i].x += step; pixels[i].y -= step; break;
                        case 2: pixels[i].x += step; break;
                        case 3: pixels[i].x += step; pixels[i].y += step; break;
                        case 4: pixels[i].y += step; break;
                        case 5: pixels[i].x -= step; pixels[i].y += step; break;
                        case 6: pixels[i].x -= step; break;
                        case 7: pixels[i].x -= step; pixels[i].y -= step; break;
                    }
                }
            }
        }

        function render() {
            ctx.globalAlpha = fadeAlpha;
            ctx.fillStyle = "#000";
            ctx.fillRect(0, 0, w, h);
            ctx.globalAlpha = 1;

            draw();
            animate();
            requestAnimationFrame(render);
        }

        emit();
        setInterval(emit, spawnRate);
        requestAnimationFrame(render);
    </script>
</body>
</html>