library / text & typography / kinetic-typography-swarm
live - 60fps 1920 x 1080
kinetic-typography-swarm.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>Kinetic Typography Swarm</title>
<style>
    body {
        background-color: #1a1a1a;
        color: white;
        margin: 0;
        overflow: hidden;
        font-family: 'Courier New', Courier, monospace;
        display: flex;
        justify-content: center;
        align-items: center;
        text-align: center;
        height: 100vh;
    }
    canvas {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 1;
    }
    h1 {
        z-index: 2;
        font-size: 4rem;
        color: #fff;
    }

</style>
</head>
<body>
    <h1>KINETIC</h1>
    <canvas id="swarmCanvas"></canvas>
<script>
    const canvas = document.getElementById('swarmCanvas');
    const ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    let particles = [];
    const chars = ['$', '#', '@', '&', '%', '}', '{', ']', '['];
    const mouse = {
        x: undefined,
        y: undefined,
    };

    window.addEventListener('mousemove', (event) => {
        mouse.x = event.clientX;
        mouse.y = event.clientY;
    });

    class Particle {
        constructor(x, y, char, color, size) {
            this.x = x;
            this.y = y;
            this.char = char;
            this.color = color;
            this.size = size;
            this.baseX = this.x;
            this.baseY = this.y;
            this.density = (Math.random() * 30) + 1;
        }

        draw() {
            ctx.fillStyle = this.color;
            ctx.font = this.size + 'px Courier New';
            ctx.fillText(this.char, this.x, this.y);
        }

        update() {
            let dx = mouse.x - this.x;
            let dy = mouse.y - this.y;
            let distance = Math.sqrt(dx * dx + dy * dy);
            let forceDirectionX = dx / distance;
            let forceDirectionY = dy / distance;
            let maxDistance = 200;
            let force = (maxDistance - distance) / maxDistance;

            if (distance < maxDistance) {
                this.x -= forceDirectionX * this.density * 0.5;
                this.y -= forceDirectionY * this.density * 0.5;
            } else {
                if (this.x !== this.baseX) {
                    let dx = this.x - this.baseX;
                    this.x -= dx / 10;
                }
                if (this.y !== this.baseY) {
                    let dy = this.y - this.baseY;
                    this.y -= dy / 10;
                }
            }
        }
    }

    function init() {
        particles = [];
        for (let i = 0; i < 1000; i++) {
            let x = Math.random() * canvas.width;
            let y = Math.random() * canvas.height;
            let char = chars[Math.floor(Math.random() * chars.length)];
            let color = 'white';
            let size = Math.random() * 15 + 5;
            particles.push(new Particle(x, y, char, color, size));
        }
    }

    function animate() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        for (let i = 0; i < particles.length; i++) {
            particles[i].update();
            particles[i].draw();
        }
        requestAnimationFrame(animate);
    }

    init();
    animate();

    window.addEventListener('resize', function(){
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        init();
    });

</script>
</body>
</html>

About this animation

Letters swarming and forming shapes.

Under the hood

Text-to-Particle Canvas Mapping

This highly advanced effect first draws a large string of text onto a hidden off-screen canvas. It then scans the canvas data using getImageData() to find non-transparent pixels. Particles are then spawned at those exact pixel locations, forming the shape of the words before exploding interactively.

Source Code
Source copied
Partner hosting options for the copied effect:
Vercel Netlify Hostinger