Why I can't clear a canvas with another empty canvas in HTML5?

Viewed 80
const buffer = document.createElement('canvas');
const context = buffer.getContext('2d');
const canvas = document.getElementById('canvas');
const screen = canvas.getContext('2d');
// buffer canvas and screen canvas have same width and height

// draw a circle on buffer canvas
context.beginPath();
context.arc(50, 50, 10, 0, Math.PI * 2);
context.fillStyle = 'rgba(156, 39, 176,1)';
context.fill();

// render to screen canvas (working)
screen.drawImage(buffer, 0, 0);

// clear canvas when click
canvas.addEventListener('click', () => {
  context.clearRect(0, 0, buffer.width, buffer.height);
  screen.drawImage(buffer, 0, 0);   // not working
  // only working with `screen.clearRect(0, 0, canvas.width, canvas.height);`
})

Like the code above, when using an empty canvas to clear another canvas, it's not working. (HTML only with <canvas id="canvas"></canvas> tag). Live Demo on https://jsfiddle.net/wjvtzng7/

1 Answers

If we write down what you are doing, we get:

One offscreen canvas "buffer", and one visible canvas "screen".

Step by step,

  • draw a circle on "buffer"
    At this stage
    • "buffer" represents a circle,
    • "screen" represents an empty image (transparent pixels).
  • draw "buffer" on "screen" canvas
    • "buffer" represents a circle,
    • "screen" represents a circle
  • clear "buffer"
    • "buffer" represents an empty image (transparent pixels)
    • "screen" represents a circle
  • draw "buffer" on "screen" canvas
    • "buffer" represents an empty image (transparent pixels)
    • "screen" represents a circle

It seems your confusion comes from the last bullet. But this operation could be rewritten as

  • draw an empty image (transparent pixels) to an image that represents a circle.

This indeed does nothing... at least in normal compositing mode source-over where drawing a fully transparent pixel does nothing. See alpha-compositing for more reads about it.

So if you wish to clear your "screen" canvas, you indeed need to clear it using the clearRect() method of the screen context. There are other ways, but don't use them.



Now, I felt I should also point out that there are other compositing modes than source-over available, and that what you expected can actually be done with one of these: copy.

const buffer = document.createElement('canvas');
const context = buffer.getContext('2d');
const canvas = document.getElementById('canvas');
const screen = canvas.getContext('2d');

// initialize
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
buffer.width = window.innerWidth;
buffer.height = window.innerHeight;
// draw a circle
context.beginPath();
context.arc(50, 50, 10, 0, Math.PI * 2);
context.fillStyle = 'rgba(156, 39, 176,1)';
context.fill();

// render to screen canvas
screen.drawImage(buffer, 0, 0);

// clear canvas when click
canvas.addEventListener('click', () => {
  context.clearRect(0, 0, buffer.width, buffer.height);
  // only the next drawing operation on screen will be visible
  // everything else will get cleared out
  screen.globalCompositeOperation = "copy";
  screen.drawImage(buffer, 0, 0);
  // set back to default mode
  screen.globalCompositeOperation = "sourc-over";
})
<canvas id="canvas"></canvas>

Related