library / 3d & webgl / tunnel
live - 60fps 1920 x 1080
tunnel.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>Tunnel</title>
<meta name="category" content="Space">
<style>
    body { margin: 0; background-color: #000; overflow: hidden; }
    canvas { display: block; }
</style>
</head>
<body>
<canvas id="tunnelCanvas"></canvas>
<script>
    const canvas = document.getElementById('tunnelCanvas');
    const ctx = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    const numLines = 200;
    const lines = [];
    let speed = 2;
    let rotation = 0;

    class Line {
        constructor() {
            this.reset();
        }

        reset() {
            this.x = 0;
            this.y = 0;
            this.z = Math.random() * canvas.width;
            this.angle = Math.random() * Math.PI * 2;
            this.radius = Math.random() * (canvas.width / 4) + (canvas.width / 8);
        }

        update() {
            this.z -= speed;
            if (this.z < 1) {
                this.reset();
            }
        }

        draw() {
            const scale = canvas.width / this.z;
            const x2d = Math.cos(this.angle + rotation) * this.radius * scale + canvas.width / 2;
            const y2d = Math.sin(this.angle + rotation) * this.radius * scale + canvas.height / 2;
            const prev_scale = canvas.width / (this.z + speed * 20);
            const prev_x2d = Math.cos(this.angle + rotation) * this.radius * prev_scale + canvas.width / 2;
            const prev_y2d = Math.sin(this.angle + rotation) * this.radius * prev_scale + canvas.height / 2;
            
            const brightness = 1 - this.z / canvas.width;

            ctx.beginPath();
            ctx.moveTo(prev_x2d, prev_y2d);
            ctx.lineTo(x2d, y2d);
            ctx.strokeStyle = `rgba(200, 220, 255, ${brightness * 0.7})`;
            ctx.lineWidth = scale * 1.5;
            ctx.stroke();
        }
    }

    function init() {
        for (let i = 0; i < numLines; i++) {
            lines.push(new Line());
        }
    }

    function animate() {
        ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        
        rotation += 0.001;

        lines.forEach(line => {
            line.update();
            line.draw();
        });

        requestAnimationFrame(animate);
    }

    window.addEventListener('resize', () => {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        lines.length = 0;
        init();
    });

    init();
    if (typeof window.isThumbnail !== 'undefined' && window.isThumbnail) {
        for(let i=0; i<120; i++) {
            ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            rotation += 0.001;
            lines.forEach(line => {
                line.update();
                line.draw();
            });
        }
    } else {
        animate();
    }
</script>
</body>
</html>

About this animation

A mesmerizing 3D tunnel effect that pulls the viewer in.

Under the hood

Fractal 3D Perspective

By drawing concentric rings consisting of multiple squares, and assigning deeper z-indices a smaller scale and darker color, the canvas creates an endless tunnel descent. Iterative loops draw the shape progressively smaller toward the vanishing point.

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