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
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 dependantdraw()
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.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 usingsave()
before each asynchronous function andrecovery()
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 thesetTimeout()
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