0

I'm adding multiple rectangles in canvas which could collide with each other. The outer stroke should be displayed on the outer part of both rectangles or the rectangle shapes should be merged in to one producing the expected result.

See picture bellow

enter image description here

It has to be cut because it will display the content under the canvas. See live example with background image: https://jsfiddle.net/0qpgf5un/

In the code example bellow rectangles are being added on top of each other as you can see in the first example of the picture.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;

ctx.fillStyle = "red";
ctx.rect(0, 0, 600, 600);
ctx.fill();

ctx.clearRect(offsetX,offsetY, w, h);
ctx.strokeRect(offsetX, offsetY, w, h);

ctx.clearRect(offsetX-50,offsetY+50, w, h);
ctx.strokeRect(offsetX-50, offsetY+50, w, h);

Is there ways to achieve it without writing complex calculations of each path, since the collision of rectangles can be unintentional and diverse ?

Edit: What I am trying to achieve is a similar functionality like in youtube's feedback form where when editing screenshot you can highlight items and the border then is merged.

danronmoon
  • 3,814
  • 5
  • 34
  • 56
Giedrius
  • 129
  • 1
  • 6

3 Answers3

0

Just add one more clearRect() (the first one)

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;


ctx.fillStyle = "red";
ctx.rect(0, 0, 600, 600);
ctx.fill();

ctx.clearRect(offsetX,offsetY, w, h);
ctx.strokeRect(offsetX, offsetY, w, h);
ctx.clearRect(offsetX-50,offsetY+50, w, h);
ctx.strokeRect(offsetX-50, offsetY+50, w, h);
ctx.clearRect(offsetX,offsetY, w, h);

https://jsfiddle.net/kt3yjhpc/

F.Igor
  • 4,119
  • 1
  • 18
  • 26
0

You can skip clearing the first rectangle and then clear it after you stroke the second one.

The clearPrev function will clear the area inside the strokes of the initial rectangle.

let canvas = document.getElementById('canvas'),
    ctx = canvas.getContext('2d'),
    offsetX = 70,
    offsetY = 20,
    w = 200,
    h = 100,
    strokeWidth = 5;

ctx.fillStyle = '#F00'
ctx.rect(0, 0, 600, 600);
ctx.fill();

ctx.strokeStyle = '#0FF';
ctx.lineWidth = strokeWidth;

//ctx.clearRect(offsetX, offsetY, w, h); <-- Do not need to do this, if we clear below...
ctx.strokeRect(offsetX, offsetY, w, h);

ctx.clearRect(offsetX - 50, offsetY + 50, w, h);
ctx.strokeRect(offsetX - 50, offsetY + 50, w, h);

clearPrev(ctx, offsetX, offsetY, w, h); // Clear previous

function clearPrev(ctx, x, y, w, h) {
  let startOffset = Math.round(ctx.lineWidth / 2) - 1,
      endOffset = strokeWidth - 1;
  ctx.clearRect(x + startOffset, y + startOffset, w - endOffset, h - endOffset);
}
<canvas id="canvas" width="290" height="190"></canvas>
Mr. Polywhirl
  • 42,981
  • 12
  • 84
  • 132
0

When you want to clear the canvas with complex shapes, forget about clearRect, it's not the only one able to produce transparent pixels.

Instead, have a look at compositing.

So your shape is really border-line, but I think you'll benefit from using this already:

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');

var offsetX = 150;
var offsetY = 150;
var w = 200;
var h = 100;
ctx.lineWidth = 2;
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 600, 600);

// declare our complex shape as a single sub-path
ctx.beginPath()
ctx.rect(offsetX,offsetY, w, h);
ctx.rect(offsetX-50, offsetY+50, w, h);

// now we can paint it

// first the stroke, because we want to erase what's inside the fill-area
ctx.stroke();

// now to erase, we switch to destination-out compositing mode
ctx.globalCompositeOperation = 'destination-out';
// fill the inner path
ctx.fill();

// we're done
// If you wish to go back to normal mode later
ctx.globalCompositeOperation = 'source-over';
body { background: linear-gradient(blue,yellow); }
<canvas id="canvas" width="600" height="600"></canvas>
Kaiido
  • 123,334
  • 13
  • 219
  • 285