Can you spot some of the big issues with this vibe coded animation?

I was using an AI assistant to help me create an infinitely scrolling grid animation where the speed and direction of the animation were dependent on the mouse cursor position.

Overall, the AI assistant did a pretty great job, and you can play with it live and inspect the code: https://codepen.io/kirupa/pen/azvwVpz

Here is a video of it working:

The full code can be seen also on Github:

Now, while this animation is sorta kinda fine as it is, there are some big and small issues that we should address in order to call this ready for prime time. Can you spot what some of those issues are?

Or to put it differently, if you were someone who was well versed in web animation techniques, what are some things that you would add or do differently?

Cheers,
Kirupa :slight_smile:

Full code is here:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Grid Animation - Canvas</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            overflow: hidden;
            background: white;
            cursor: none;
            font-family: Arial, sans-serif;
        }

        #gridCanvas {
            position: fixed;
            top: 0;
            left: 0;
            background: white;
        }

        .info {
            position: fixed;
            top: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 10px 15px;
            border-radius: 5px;
            font-size: 14px;
            z-index: 1001;
            font-family: monospace;
        }
    </style>
</head>
<body>
    <canvas id="gridCanvas"></canvas>
    <div class="info" id="info">
        FPS: <span id="fps">0</span>
    </div>

    <script>
        const canvas = document.getElementById('gridCanvas');
        const ctx = canvas.getContext('2d');
        const distanceSpan = document.getElementById('distance');
        const speedSpan = document.getElementById('speed');
        const fpsSpan = document.getElementById('fps');

        let mouseX = window.innerWidth / 2;
        let mouseY = window.innerHeight / 2;
        let centerX = window.innerWidth / 2;
        let centerY = window.innerHeight / 2;
        
        let gridOffsetX = 0;
        let gridOffsetY = 0;
        let animationId;
        
        // FPS tracking
        let lastTime = performance.now();
        let frameCount = 0;
        let fps = 0;

        const gridSize = 15;

        function resizeCanvas() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
            centerX = window.innerWidth / 2;
            centerY = window.innerHeight / 2;
        }

        function drawCrosshair() {
            const crosshairSize = 30;
            const thickness = 4;
            
            // Set crosshair style
            ctx.strokeStyle = 'red';
            ctx.lineWidth = thickness;
            ctx.lineCap = 'round';
            ctx.beginPath();
            
            // Draw horizontal line
            ctx.moveTo(mouseX - crosshairSize / 2, mouseY);
            ctx.lineTo(mouseX + crosshairSize / 2, mouseY);
            
            // Draw vertical line
            ctx.moveTo(mouseX, mouseY - crosshairSize / 2);
            ctx.lineTo(mouseX, mouseY + crosshairSize / 2);
            
            ctx.stroke();
        }

        function drawGrid() {
            const width = canvas.width;
            const height = canvas.height;
            
            // Clear canvas
            ctx.clearRect(0, 0, width, height);
            
            // Set line style
            ctx.strokeStyle = 'black';
            ctx.lineWidth = 1;
            ctx.beginPath();

            if (gridOffsetX > gridSize) {
                gridOffsetX = 0;
            }

            if (gridOffsetY > gridSize) {
                gridOffsetY = 0;
            }
            
            // Draw vertical lines
            for (let x = gridOffsetX; x <= width + gridSize; x += gridSize) {
                ctx.moveTo(x, 0);
                ctx.lineTo(x, height);
            }
            
            // Draw horizontal lines
            for (let y = gridOffsetY; y <= height + gridSize; y += gridSize) {
                ctx.moveTo(0, y);
                ctx.lineTo(width, y);
            }
            
            ctx.stroke();
            
            // Draw crosshair on top
            drawCrosshair();
        }

        function updateFPS(currentTime) {
            frameCount++;
            if (currentTime - lastTime >= 1000) {
                fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
                frameCount = 0;
                lastTime = currentTime;
                fpsSpan.textContent = fps;
            }
        }

        function animate(currentTime) {
            // Update FPS
            updateFPS(currentTime);
            
            // Calculate distance and direction from center
            const deltaX = mouseX - centerX;
            const deltaY = mouseY - centerY;
            const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
            
            // Calculate maximum possible distance (corner to center)
            const maxDistance = Math.sqrt(centerX * centerX + centerY * centerY);
            
            // Calculate speed based on distance (0 to 100%)
            const speedFactor = Math.min(distance / maxDistance, 1);
            const maxSpeed = 3; // Maximum pixels per frame
            
            // Calculate velocity - always moving, speed varies by distance from center
            let velocityX = 0;
            let velocityY = 0;
            
            if (distance > 5) {
                // Calculate direction vector (normalized)
                const directionX = deltaX / distance;
                const directionY = deltaY / distance;
                
                // Apply speed based on distance from center
                velocityX = directionX * maxSpeed * speedFactor;
                velocityY = directionY * maxSpeed * speedFactor;
            }
            
            // Continuously update grid position for infinite scroll
            gridOffsetX += velocityX;
            gridOffsetY += velocityY;
            
            // Draw the grid
            drawGrid();
            
            // Continue animation
            animationId = requestAnimationFrame(animate);
        }

        // Handle window resize
        window.addEventListener('resize', resizeCanvas);

        // Track mouse movement
        document.addEventListener('mousemove', function(e) {
            mouseX = e.clientX;
            mouseY = e.clientY;

            drawCrosshair();
        });

        // Initialize
        resizeCanvas();

        // Start animation
        animate();
    </script>
</body>
</html>

Hmm:

  • Seems like there are some sharp lines and DPI issues that make the grid blurry.
  • You’d probably want a mouseout event to hide the crosshairs when you leave the grid.
  • In Safari at least, you’re not going to get >60 FPS updates unless you disable Prefer Page Rendering Updates near 60fps.
  • Ideally you’d ease between animation directions; like if your mouse moves instantly from 0, 0 to 1000, 1000, the direction and speed will statelessly update to the new value.
  • There are a bunch of useless semicolons cluttering up the JS. :2c:
1 Like

Wow! That is a great response :stuck_out_tongue:

This is what I found:

  • Negative offsets grow forever when moving leftwards or upwards, which could make the loop draw thousands of lines per frame and kill performance.
  • Crosshair gets drawn twice, once on mousemove and again in requestAnimationFrame. As a result, the event draw is pointless since the frame loop handles it anyway.
  • requestAnimationFrame(animate) runs continuously, even if velocityX and velocityY are both zero and the grid isn’t moving.
  • Motion should ideally have some acceleration, currently it’s purely linear.
  • Lastly, magic numbers everywhere!
1 Like

@madanva - your response is :fire:

(Also, welcome to the forums! :waving_hand: )

1 Like