← Mike Higgins Home
Custom Code Experiment •

Chromatophores 2

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;
        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 = ["rect", "arc", "triangle", "diamond", "cross", "star"];

        function drawShape(shape, x, y, size, pulse) {
            var s = size * pulse;
            ctx.beginPath();
            switch(shape) {
                case "rect":
                    ctx.rect(x - s/2, y - s/2, s, s);
                    break;
                case "arc":
                    ctx.arc(x, y, s / 2, 0, 2 * Math.PI);
                    break;
                case "triangle":
                    ctx.moveTo(x, y - s / 2);
                    ctx.lineTo(x + s / 2, y + s / 2);
                    ctx.lineTo(x - s / 2, y + s / 2);
                    ctx.closePath();
                    break;
                case "diamond":
                    ctx.moveTo(x, y - s / 2);
                    ctx.lineTo(x + s / 2, y);
                    ctx.lineTo(x, y + s / 2);
                    ctx.lineTo(x - s / 2, y);
                    ctx.closePath();
                    break;
                case "cross":
                    var t = s / 4;
                    ctx.rect(x - t/2, y - s/2, t, s);
                    ctx.rect(x - s/2, y - t/2, s, t);
                    break;
                case "star":
                    var spikes = 5;
                    var outerR = s / 2;
                    var innerR = s / 4.5;
                    var rot = (Math.PI / 2) * 3;
                    var step = Math.PI / spikes;
                    ctx.moveTo(x, y - outerR);
                    for (var k = 0; k < spikes; k++) {
                        ctx.lineTo(
                            x + Math.cos(rot) * outerR,
                            y + Math.sin(rot) * outerR
                        );
                        rot += step;
                        ctx.lineTo(
                            x + Math.cos(rot) * innerR,
                            y + Math.sin(rot) * innerR
                        );
                        rot += step;
                    }
                    ctx.lineTo(x, y - outerR);
                    ctx.closePath();
                    break;
            }
        }

        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[Math.floor(Math.random() * shapes.length)],
                    pulseOffset: Math.random() * Math.PI * 2,
                    pulseSpeed: 2 + Math.random() * 3
                });
            }
        }

        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);

                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>