2

I see you can clear part of the canvas using CanvasRenderingContext2D.clearRect. This function will clear up full/partial canvas based on the coordinates specified. Is it possible to clear some of the foreground drawings, but keep the background drawings intact?

Inside of react component, I have the following

  componentDidMount() {
    this.canvas = this.canvasRef.current;
    this.c = this.canvas.getContext('2d')

    this.canvas.width = window.innerWidth
    this.canvas.height = window.innerHeight

    window.addEventListener('resize', this.resize)

    this.init() 
    this.backgroundGradientAnimate() //draw background gradient
    window.requestAnimationFrame(this.step)

  }

  // generate stars
  init = () => {
    this.stars = []

    for (let i = 0; i < 150; i++) {
      const x = Math.random() * this.canvas.width;
      const y = Math.random() * this.canvas.height
      const r = Math.random() * 3
      this.stars.push(new Star(x, y, r, 'white'))
    }
  }

  draw = (star) => {
    this.c.save()
    this.c.beginPath()
    this.c.arc(star.x, star.y, star.radius, 0, Math.PI * 2, false)
    this.c.fillStyle = this.getRandomColor()
    this.c.fill();
    this.c.closePath();
    this.c.restore()
  }

  starsAnmiate = () => {
    this.stars.forEach(star => this.draw(star))
  }

  // only animate in such time
  step = (elapsedTime) => {
    let delta = elapsedTime - this.lastFrameTime || 0

    window.requestAnimationFrame(this.step)

  if (this.lastFrameTime && delta < 1000) {
    return;
  }

  this.lastFrameTime = elapsedTime

  this.starsAnmiate();
  }

The issue is that all the stars never cleared out after animations repeat itself. Is there way of only cleaning up the drawing of stars but retaining the backgroundGradient, or two drawings always have to be cleaned at the same time because they sit on the same canvas?

Overall, I'm trying to create a blinking effect of those stars. It just seems to be lots of overhead when I need to clean/redraw the entire canvas every time to simulate the blinking of each star.

[EDIT]

  1. To optimize animation effort, you can draw 2 separate canvas as HERE
jen007
  • 1,389
  • 3
  • 15
  • 19
  • 1
    Possible duplicate of [html5 - canvas element - Multiple layers](https://stackoverflow.com/questions/3008635/html5-canvas-element-multiple-layers) – IronFlare Aug 23 '19 at 18:26
  • Yes, that's very helpful, I will update my question. Is it possible to update only the opacity of stars, rather than redrawing all of them? – jen007 Aug 23 '19 at 18:52
  • Unfortunately no. It's not possible to *update* anything on a canvas once it's been drawn. All you can do is erase a section or draw over top of it. – IronFlare Aug 23 '19 at 19:00
  • I'll go ahead and move these comments into an answer, since they seem to have helped you solve your problem. – IronFlare Aug 23 '19 at 19:03
  • yes, thats fine with me. Thanks! – jen007 Aug 23 '19 at 19:04

1 Answers1

0

There are a few ways to do this, but they all involve the use of some sort of workaround.

In essence, it sounds like you're looking to have multiple layers on a single canvas, such that you can erase from one and not the others. Unfortunately, there is no native functionality that allows you to do this.

Canvases are set up to have one (and only one) context, either 2D or 3D. When you draw onto a context, you change the color of the individual pixels that make up that canvas. JavaScript has no concept of what's underneath a certain drawing, because it makes changes to each pixel of the canvas directly (you could also argue that JS doesn't even have a concept of a drawing, but that's not particularly relevant here).

If you wanted to have multiple layers that support erasure independently, you could do one of two things, both of which have drawbacks:

  • Stack multiple canvas elements on top of one another and modify each individually. Note that with this method, you won't be able to easily export the content of all canvases at once.
  • Maintain a one-dimensional Uint8ClampedArray of pixels for each layer and draw each array directly using ctx.putImageData in layered order. Using typed arrays is faster than raw arrays, but the data structure of this is conceptually kind of wonky. You can read more about this approach here and here.
IronFlare
  • 2,287
  • 2
  • 17
  • 27
  • 1
    Your second point makes no sense. putImageData will **replace** the pixels in its area to the ones of the ImageData, no way to make any compositing, you cannot put "on top" of any existing content. The biggest drawback for the first approach is memory and painting time, doing the layering through CSS is far more work than say ctx.drawImage(canvas1,0,0); ctx.drawImage(canvas2,0,0) etc. The easiest is clear all and redraw all every frame. If you have a layer that doesn't change too often, then consider making it it's own out of DOM canvas. – Kaiido Aug 24 '19 at 01:02
  • @Kaiido The part I focused on in my answer was maintaining a record of the content of the pixels on each layer. The ultimate idea was to use an OffscreenCanvas or use `get`, merge pixel values, then use `put` to add it back to the main canvas, but I failed to mention that, so my answer came off as vague. Thanks for pointing this out. :) – IronFlare Aug 24 '19 at 01:40