library / particles & systems / envato-glowing-fireflies
live - 60fps 1920 x 1080
envato-glowing-fireflies.html - self-contained
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Glowing Fireflies</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #020617;
            /* Very dark slate */
            /* Add some depth to the night sky */
            background-image: radial-gradient(circle at 50% 100%, #0f172a 0%, #020617 80%);
        }
        canvas {
            display: block;
            width: 100vw;
            height: 100vh;
        }
    </style>
</head>
<body>
    <canvas id="fireflyCanvas"></canvas>
    <script>
        const canvas = document.getElementById('fireflyCanvas');
        const ctx = canvas.getContext('2d');
        let width, height;
        let fireflies = [];
        const config = {
            count: 150,
            color: '#d9f99d', // Lime/yellow glow
            sizeMin: 1,
            sizeMax: 2.5,
            speed: 0.5,
            wander: 0.2 // How erratic they move (Brownian motion)
        };
        function resize() {
            width = canvas.width = window.innerWidth;
            height = canvas.height = window.innerHeight;
        }
        class Firefly {
            constructor() {
                this.x = Math.random() * width;
                this.y = Math.random() * height;
                this.vx = (Math.random() - 0.5) * config.speed;
                this.vy = (Math.random() - 0.5) * config.speed;
                this.baseSize = config.sizeMin + Math.random() * (config.sizeMax - config.sizeMin);
                // For pulsing glow
                this.angle = Math.random() * Math.PI * 2;
                this.pulseSpeed = 0.02 + Math.random() * 0.03;
                // For organic drifting
                this.wanderAngle = Math.random() * Math.PI * 2;
            }
            update() {
                // Organic erratic drifting (Brownian motion)
                this.wanderAngle += (Math.random() - 0.5) * 0.5;
                this.vx += Math.cos(this.wanderAngle) * config.wander * 0.1;
                this.vy += Math.sin(this.wanderAngle) * config.wander * 0.1;
                // Speed limits
                const currentSpeed = Math.sqrt(this.vx * this.vx + this.vy * this.vy);
                if (currentSpeed > config.speed * 2) {
                    this.vx = (this.vx / currentSpeed) * config.speed * 2;
                    this.vy = (this.vy / currentSpeed) * config.speed * 2;
                }
                this.x += this.vx;
                this.y += this.vy;
                // Screen wrapping
                if (this.x < 0) this.x = width;
                if (this.x > width) this.x = 0;
                if (this.y < 0) this.y = height;
                if (this.y > height) this.y = 0;
                // Update pulsing
                this.angle += this.pulseSpeed;
            }
            draw() {
                // Calculate opacity using sine wave for smooth throbbing glow
                const alpha = (Math.sin(this.angle) * 0.5 + 0.5) * 0.8 + 0.1;
                const size = this.baseSize;
                // Core
                ctx.beginPath();
                ctx.arc(this.x, this.y, size, 0, Math.PI * 2);
                ctx.fillStyle = `rgba(217, 249, 157, ${alpha})`;
                ctx.fill();
                // Diffuse Glow
                if (alpha > 0.4) {
                    ctx.beginPath();
                    const gradient = ctx.createRadialGradient(this.x, this.y, size, this.x, this.y, size * 6);
                    gradient.addColorStop(0, `rgba(163, 230, 53, ${alpha * 0.5})`);
                    gradient.addColorStop(1, 'rgba(163, 230, 53, 0)');
                    ctx.fillStyle = gradient;
                    ctx.arc(this.x, this.y, size * 6, 0, Math.PI * 2);
                    ctx.fill();
                }
            }
        }
        function init() {
            resize();
            fireflies = [];
            let c = window.innerWidth < 768 ? Math.floor(config.count / 2) : config.count;
            for (let i = 0; i < c; i++) {
                fireflies.push(new Firefly());
            }
        }
        function animate() {
            ctx.clearRect(0, 0, width, height);
            // Optional: Draw some ambient foreground elements or background trees here
            // Draw fireflies
            ctx.globalCompositeOperation = 'screen';
            for (let i = 0; i < fireflies.length; i++) {
                fireflies[i].update();
                fireflies[i].draw();
            }
            ctx.globalCompositeOperation = 'source-over';
            requestAnimationFrame(animate);
        }
        window.addEventListener('resize', init);
        init();
        animate();
    </script>
</body>
</html>

About this animation

Jittery, bioluminescent canvas particles that drift organically through a dark foreground.

Under the hood

Brownian Motion Sub-Routines

To make these fireflies look like living organisms rather than rigid code, they employ Brownian Motion. The velocity vector incorporates a 'wander angle' that shifts randomly every single frame (Math.sin(this.wanderAngle)). This prevents them from moving in straight lines, simulating erratic, organic flight.

Source Code
Copied to clipboard