skip to Main Content

I tried developing a graph drawing web app using a canvas which includes animations for lines as this:

function animateLine(x1, y1, x2, y2) {
    ctx.lineWidth = 5;
    const nrFrames = 30;
    const dy = y2 - y1;
    const dx = x2 - x1;
    const incx = dx / nrFrames;
    const incy = dy / nrFrames;
    let tmpx = x1, tmpy = y1;
    ctx.strokeStyle = 'rgba(11, 158, 131, 0.4)';
    for (let i = 0; i < nrFrames; i++) {
        setTimeout(() => {
            tmpx += incx;
            tmpy += incy;
            ctx.beginPath();
            ctx.moveTo(x1, y1);
            ctx.lineTo(tmpx, tmpy);
            ctx.stroke();
        }, i * 16);
    }
}

But also for circles as this:

class Circle {
    constructor(x, y, radius, name) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.name = name;
    }
    draw() {
        ctx.strokeStyle = 'black';
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.stroke();
        ctx.fillStyle = 'rgb(110, 255, 255)';
        ctx.fill();
        ctx.fillStyle = 'black';
        ctx.fillText(this.name, this.x, this.y);
    }
}

Given the shared context and the fact that the functions have to run asynchronously, the strokes never have the intended color.

I see that I could add strokeStyle = ‘…’ on the line exactly above every stroke made, but I don’t believe that’s the best solution since with a lot of asychronous functions you will never have a guarantee.

Put in general terms: how is the problem of different style requirements for asychronous functions that use the same context managed?

2

Answers


  1. There are basically two options: synchronizing the different parts that draw something or actually putting strokeStyle = ... before each .stroke() statement.

    If you want to synchronize the code, you will need a centralized draw() function that calls all dependant draw() functions in turns. This makes sure they do not interfere with each other (like changing the color).

    Synchronizing is definitely a bit more complex but the code will be much cleaner ultimately. For your animation this would mean you cannot use setTimeout in this way anymore.

    Login or Signup to reply.
  2. One solution to this problem is to use the save()/restore() methods – this allows you to save the current state of the context, including the transformation matrix, current styles, etc. This solution will allow you to maintain asynchrony without fear of mixing styles. By using save() before each asynchronous function and recovery() after, you encapsulate each function’s style changes in its own context. In my opinion, this feature is crucial, especially when we are talking about animations that usually use it. The second thing is the lack of collisions with the setTimeout() implementation, where you can control the execution time of each asynchronous function.

    https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save

    https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/restore

    function animateLine(x1, y1, x2, y2) {
                ctx.lineWidth = 5;
                const nrFrames = 30;
                const dy = y2 - y1;
                const dx = x2 - x1;
                const incx = dx / nrFrames;
                const incy = dy / nrFrames;
                let tmpx = x1, tmpy = y1;
                for (let i = 0; i < nrFrames; i++) {
                    setTimeout(() => {
                        ctx.save();
                        ctx.strokeStyle = 'rgba(11, 158, 131, 0.4)';
                        tmpx += incx;
                        tmpy += incy;
                        ctx.beginPath();
                        ctx.moveTo(x1, y1);
                        ctx.lineTo(tmpx, tmpy);
                        ctx.stroke();
                        ctx.restore();
                    }, i * 16);
                }
            }
            class Circle {
                constructor(x, y, radius, name) {
                    this.x = x;
                    this.y = y;
                    this.radius = radius;
                    this.name = name;
                }
                draw() {
                    ctx.save();
                    ctx.strokeStyle = 'black';
                    ctx.beginPath();
                    ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
                    ctx.stroke();
                    ctx.fillStyle = 'rgb(110, 255, 255)';
                    ctx.fill();
                    ctx.fillStyle = 'black';
                    ctx.fillText(this.name, this.x, this.y);
                    ctx.restore();
                }
            }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search