Ayush Rajniwal
Ayush Rajniwal

Reputation: 85

Erasing only paticular element of canvas in JS

I want to create something like scratch card. I created a canvas and added text to it.I than added a box over the text to hide it.Finally write down the code to erase(scratch) that box.

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.font = "30px Arial";
ctx.fillText("Hello World",10,50); 
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle='red';
ctx.fillRect(0,0,500,500);
function myFunction(event) {
  var x = event.touches[0].clientX;
  var y = event.touches[0].clientY;
  document.getElementById("demo").innerHTML = x + ", " + y;
  ctx.globalCompositeOperation = 'destination-out';
  ctx.arc(x,y,30,0,2*Math.PI);
  ctx.fill();
}

But the problem is it delete the text also.
How could I only delete that box not the text?

Upvotes: 0

Views: 155

Answers (2)

Kaiido
Kaiido

Reputation: 137123

Canvas context keeps only one drawing state, which is the one rendered. If you modify a pixel, it won't remember how it was before, and since it has no built-in concept of layers, when you clear a pixel, it's just a transparent pixel.

So to achieve what you want, the easiest is to build this layering logic yourself, e.g by creating two "off-screen" canvases, as in "not appended in the DOM", one for the scratchable area, and one for the background that should be revealed.

Then on a third canvas, you'll draw both canvases every time. It is this third canvas that will be presented to your user:

var canvas = document.getElementById("myCanvas");
// the context that will be presented to the user
var main = canvas.getContext("2d"); 
// an offscreen one that will hold the background
var background = canvas.cloneNode().getContext("2d");
// and the one we will scratch
var scratch = canvas.cloneNode().getContext("2d");
generateBackground();
generateScratch();

drawAll();

// the events handlers
var down = false;
canvas.onmousemove = handlemousemove;
canvas.onmousedown = handlemousedown;
canvas.onmouseup = handlemouseup;

function drawAll() {
  main.clearRect(0,0,canvas.width,canvas.height);
  main.drawImage(background.canvas, 0,0);
  main.drawImage(scratch.canvas, 0,0);
}

function generateBackground(){
  background.font = "30px Arial";
  background.fillText("Hello World",10,50);
}
function generateScratch() {
  scratch.fillStyle='red';
  scratch.fillRect(0,0,500,500);
  scratch.globalCompositeOperation = 'destination-out';
}

function handlemousedown(evt) {
  down = true;
  handlemousemove(evt);
}
function handlemouseup(evt) {
  down = false;
}
function handlemousemove(evt) {
  if(!down) return;
  var x = evt.clientX - canvas.offsetLeft;
  var y = evt.clientY - canvas.offsetTop;
  scratch.beginPath();
  scratch.arc(x, y, 30, 0, 2*Math.PI);
  scratch.fill();
  drawAll();
}
<canvas id="myCanvas"></canvas>

Now, it could all have been done on the same canvas, but performance wise, it's probably not the best, since it implies generating an overly complex sub-path that should get re-rendered at every draw, also, it is not much easier to implement:

var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
ctx.font = '30px Arial';
drawAll();

// the events handlers
var down = false;
canvas.onmousemove = handlemousemove;
canvas.onmousedown = handlemousedown;
canvas.onmouseup = handlemouseup;

function drawAll() {
  ctx.globalCompositeOperation = 'source-over';
  // first draw the scratch pad, intact
  ctx.fillStyle = 'red';
  ctx.fillRect(0,0,500,500);
  // then erase with the currently being defined path
  // see 'handlemousemove's note
  ctx.globalCompositeOperation = 'destination-out';
  ctx.fill();
  // finally draw the text behind
  ctx.globalCompositeOperation = 'destination-over';
  ctx.fillStyle = 'black';
  ctx.fillText("Hello World",10,50);
}

function handlemousedown(evt) {
  down = true;
  handlemousemove(evt);
}
function handlemouseup(evt) {
  down = false;
}
function handlemousemove(evt) {
  if(!down) return;
  var x = evt.clientX - canvas.offsetLeft;
  var y = evt.clientY - canvas.offsetTop;
  // note how here we don't create a new Path,
  // meaning that all the arcs are being added to the single one being rendered
  ctx.moveTo(x, y);
  ctx.arc(x, y, 30, 0, 2*Math.PI);
  drawAll();
}
<canvas id="myCanvas"></canvas>

Upvotes: 1

T.J. Crowder
T.J. Crowder

Reputation: 1075427

How could I only delete that box not the text?

You can't, you'll have to redraw the text. Once you've drawn the box over the text, you've obliterated it, it doesn't exist anymore. Canvas is pixel-based, not shape-based like SVG.

Upvotes: 1

Related Questions