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

I’m going to leave this up for one more week (after posting about it on the site and signal boosting one more time) before shipping out the books! :slight_smile:

Hi Everyone,
What a great thread! I’ve been enjoying “vibe coding,” but I also share the concern that it can drift into messy or substandard patterns. Since I’m not a hardcore programmer, I thought I’d see how far I could get using ChatGPT-5.

As a first step, I gave it the original code and simply asked:

“What is substandard about this code? Please optimize it and ensure it follows expert best modern practices. I would like to download the resulting files in a zip file if possible.”

It produced this first pass: V1 ChatGPT5 Improvements Only (CodePen)

Then I shared the observations that [@krilnon] and [@madanva] had made in this thread, and asked ChatGPT to map out:

  • Which issues it had already fixed,

  • Which ones it had missed,

  • Any additional ones it spotted on its own.

It produced a markdown file (which I further edited): V1 Comparative Summary (Markdown)

From there, I asked it to implement all the missing items, and for anything subjective/contradictory, to add HUD checkboxes so you can toggle them on/off.

That gave me a second more collaborative version: V2 sheathe + ChatGPT5 Version [CodePen]

Here is a summary: V2 Implementation Summary [Markdown]


My questions for you all, if interested:

  1. How did ChatGPT5 do with no help? V1 (CodePen)
  2. Any thoughts on the collaborative version? V2 [CodePen]

I definitely lean more toward thinking that non-experts can get very good results with the right approach - especially using the best-trained AI models for code reviews and asking targeted questions, combined with rapid learning along the way. I’m curious to hear others’ experience with this approach?

P.S. I’d love to see how Claude, Gemini and Grok would do on this experiment with my same initial prompt.
P.P.S. Hi @kirupa I’ve been a fan since back in the days when you were making games with Flash! Have always loved your content!

1 Like

@sheathe - welcome to the forums! All those years feel like they were here just yesterday.

Regarding your post, that is a good use of having AI fix issues made by the other AI :innocent:

The biggest thing it is missing is catching the missing delta frame feature where, without addressing it, your animation will run at varying speed on devices with different refresh rates.