0

On my canvas I have 2 types of circles :

those I created directly from the context (= decorativeStars):

 drawDecorativeStar = (decorativeStar) => {
        this.ctx.beginPath();
        this.ctx.arc(decorativeStar.x, decorativeStar.y, decorativeStar.radius, 0, 2 * Math.PI);
        this.ctx.fillStyle = "rgba(254, 255, 242," + decorativeStar.alpha + ")";
        this.ctx.fill();
        this.ctx.closePath();

    }

and those created as Path2D because I wanted them to be clickable (=planetPaths):

createPlanetPaths = (planets) => {
        for (var i = 0; i < planets.length; i++) {
            this.planetPaths[i] = {
                ref: new Path2D(),
                x: Math.random() * WIDTH_CANVAS,
                y: Math.random() * HEIGHT_CANVAS,
                radius: this.getPlanetRadius(planets[i]),
                color: planets[i].color,
            }
        }
    }

drawPlanets = (planetPaths) => {

        for (let i = 0; i < planetPaths.length; i++) {
            planetPaths[i].ref.arc(planetPaths[i].x, planetPaths[i].y, planetPaths[i].radius, 0, 2 * Math.PI);
            planetPaths[i].ref.gradient = this.ctx.createLinearGradient((planetPaths[i].x - planetPaths[i].radius), (planetPaths[i].y - planetPaths[i].radius), 1.02 * (planetPaths[i].x + planetPaths[i].radius), 1.02 * (planetPaths[i].y + planetPaths[i].radius));
            planetPaths[i].ref.gradient.addColorStop(0, planetPaths[i].color);
            planetPaths[i].ref.gradient.addColorStop(1, 'red');
            this.ctx.fillStyle = planetPaths[i].ref.gradient;
            this.ctx.fill(planetPaths[i].ref);
        }

    };

Now i'd like to animate these circles, using requestAnimationFrame. My problem is that this.ctx.clearRect(0, 0, WIDTH_CANVAS, HEIGHT_CANVAS); seem to have no effect on the Path2D objects, while it works for the others.

Is there another way to clear Path2D objects?

EDIT, here my updateCanvas method, to generate the animation:

updateCanvas() {

        this.ctx.clearRect(0, 0, WIDTH_CANVAS, HEIGHT_CANVAS);

        for (let i = 0; i < this.planetPaths.length; i++) {
            var planetPath = this.planetPaths[i];
            if (planetPath.x < WIDTH_CANVAS) {
                planetPath.x += 1;
            } else {
                planetPath.x = 0;
            }
        }


        for (let i = 0; i < this.decorativeStars.length; i++) {
            var star = this.decorativeStars[i];
            if (star.decreasing == true) {
                star.alpha -= star.decreasingIncreasingRatio;
                if (star.alpha < 0.10) { star.decreasing = false; }
            }
            else {
                star.alpha += star.decreasingIncreasingRatio;
                if (star.alpha > 0.95) { star.decreasing = true; }
            }
            // star.x+=0.01;
        }

        this.drawDecorativeStars(this.decorativeStars);
        this.drawPlanets(this.planetPaths);

        this.myReq = requestAnimationFrame(this.updateCanvas);

    }
Julien Berthoud
  • 721
  • 8
  • 24

1 Answers1

2

You are using the Path2D object incorrectly.

In the drawPlanets function you are adding sub-paths to the path each time you call draw.

planetPaths[i].ref.arc(planetPaths[i].x, planetPaths[i].y, planetPaths[i].radius, 0, 2 * Math.PI);

Thus every time you draw the path with this.ctx.fill(planetPaths[i].ref); you draw all the sub paths you have added up to that time.

Using Path2D

  • You can not clear the Path2D object.
  • You only add the path you want rendered, once.
  • To change the path you need to create a new Path2D or transform the path when you render it.

Fix

Create the path and all unchanging states once (init), then render many times (each frame)

You code should look more like...

createPlanetPaths = planets => {
    for (const planet of planets) {
        const path = new Path2D();
        const x = Math.random() * WIDTH_CANVAS;
        const y = Math.random() * HEIGHT_CANVAS;
        const radius =  this.getPlanetRadius(planet);

        path.arc(x, y, radius, 0, 2 * Math.PI);

        const gradient = this.ctx.createLinearGradient((px - radius), (y - radius), 1.02 * (x + radius), 1.02 * (y + radius));
        gradient.addColorStop(0, planet.color);
        gradient.addColorStop(1, 'red');

        this.planetPaths[i] = {path, gradient};
    }
}

drawPlanets = planetPaths => {
    for (const planetPath of planetPaths) {
        this.ctx.fillStyle = planetPath.gradient;
        this.ctx.fill(planetPath.path);
    }
}
Community
  • 1
  • 1
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • Thanks for answering my question. I'm trying to implement your solution but I'm not sure to fully understand it. I could successfully replace my code by yours and obtain the same drawing on my canvas. Now when I want to animate the planets, my approach was basically to modify the x and/or y value of each Path2D object (see my edit above). Is it the way to go? – Julien Berthoud Jan 13 '20 at 10:53
  • @JulienBerthoud If you change the path shape you must create a new `Path2D`. If you are just changing the position you can use use `ctx.setTransform`, or `ctx.translate` or create a new path and add original path with a `DOMMatrix` that positions it. Or create a new path2D from scratch. Seams like a lot of work for a circular click region when a single line can do the same `if (Math.hypot(mouse.x - planet.x, mouse.y - planet.y) < planet.radius) { /* mouse is over the planet */ }` – Blindman67 Jan 13 '20 at 11:22