How to smooth out the line animation in an HTML5 canvas?

community!

I am working on a project using HTML5 Canvas and JavaScript/jQuery, where I have multiple animated routes between points on a grid. The animation should smoothly draw lines from point A to point B, but I’m having trouble achieving a smoother movement for the line.

enter image description here

Here’s the code I’m using:

$(document).ready(function () {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const points = [];
    const routes = [];
    const pointRadius = 10;
    const gridSize = 60; // Spacing between points
    const numRows = Math.floor(canvas.width / gridSize);
    const numCols = Math.floor(canvas.height / gridSize);
    const animationDuration = 2000; // Total animation time (adjustable)

    // Function to draw a point
    function drawPoint(x, y, color = 'black') {
        ctx.beginPath();
        ctx.arc(x, y, pointRadius, 0, 2 * Math.PI);
        ctx.fillStyle = color;
        ctx.fill();
        ctx.strokeStyle = color;
        ctx.stroke();
    }

    // Function to draw the full curve from A to B
    function drawFullCurve(pointA, pointB, colorStart, colorEnd) {
        const midX = (pointA.x + pointB.x) / 2;
        const midY = (pointA.y + pointB.y) / 2 - 50; // Curve elevation

        const gradient = ctx.createLinearGradient(pointA.x, pointA.y, pointB.x, pointB.y);
        gradient.addColorStop(0, colorStart);
        gradient.addColorStop(1, colorEnd);

        ctx.beginPath();
        ctx.moveTo(pointA.x, pointA.y);
        ctx.quadraticCurveTo(midX, midY, pointB.x, pointB.y);
        ctx.strokeStyle = gradient;
        ctx.lineWidth = 2;
        ctx.stroke();
    }

    // Function to animate the reveal of the curve from A to B
    function animateCurve(pointA, pointB, colorStart, colorEnd, duration) {
        let startTime = performance.now();

        function animateStep(timestamp) {
            const elapsedTime = timestamp - startTime;
            const progress = Math.min(elapsedTime / duration, 1); // Progress from 0 to 1

            // Do not clear the entire canvas! Keep previous routes
            ctx.save();
            ctx.beginPath();
            ctx.moveTo(pointA.x, pointA.y);

            // Calculate intermediate position using linear interpolation
            const t = progress;
            const midX = (pointA.x + pointB.x) / 2;
            const midY = (pointA.y + pointB.y) / 2 - 50;

            const revealX = (1 - t) * ((1 - t) * pointA.x + t * midX) + t * ((1 - t) * midX + t * pointB.x);
            const revealY = (1 - t) * ((1 - t) * pointA.y + t * midY) + t * ((1 - t) * midY + t * pointB.y);

            ctx.quadraticCurveTo(midX, midY, revealX, revealY);
            ctx.clip();

            drawFullCurve(pointA, pointB, colorStart, colorEnd);

            ctx.restore();

            drawPoint(pointA.x, pointA.y, colorStart);
            drawPoint(pointB.x, pointB.y, colorEnd);

            if (progress < 1) {
                requestAnimationFrame(animateStep);
            }
        }

        requestAnimationFrame(animateStep);
    }

    // Generate points
    for (let i = 0; i < numRows; i++) {
        for (let j = 0; j < numCols; j++) {
            const x = i * gridSize + gridSize / 2;
            const y = j * gridSize + gridSize / 2;
            points.push({ x, y });
            drawPoint(x, y);
        }
    }

    // Generate 20 random routes
    function generateRandomRoutes() {
        for (let i = 0; i < 20; i++) {
            const startPoint = points[Math.floor(Math.random() * points.length)];
            const endPoint = points[Math.floor(Math.random() * points.length)];
            routes.push({ startPoint, endPoint });
        }
    }

    generateRandomRoutes();

    // Function to animate routes simultaneously
    function animateRoutes() {
        routes.forEach(route => {
            const colorStart = 'blue';
            const colorEnd = 'red';
            const { startPoint, endPoint } = route;

            // Execute the animation for each route without clearing the canvas
            animateCurve(startPoint, endPoint, colorStart, colorEnd, animationDuration);
        });
    }

    // Execute the animation of the routes
    animateRoutes();
});

Problem:
The animation is not as smooth as I would like. Instead, the line seems to be “drawn” abruptly from A to B. What I want is for the line to be drawn smoothly, as if it’s being traced over time, instead of appearing all at once.

Question:
How can I smooth out this animation so that the line appears more fluid? Any tips on improving the interpolation calculation or how the line is drawn would be greatly appreciated!

Thank you in advance for your help!