Reputation: 41
I'm trying to create an array of shapes that overlap. But I'm having difficulty preventing those shapes stacking on top of one-another.
I guess I want them to mesh together, if that makes sense?
Here's the code:
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x,y);
for (j=0;j<rectQTY;j++){ // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
// Degrees to rotate for next position
overlap_context.rotate((Math.PI/180)*360/rectQTY);
}
And here's my jsFiddle: http://jsfiddle.net/Q8yjP/
And here's what I'm trying to achieve:
Any help or guidance would be greatly appreciated!
Upvotes: 4
Views: 1791
Reputation: 378
Using pure JavaScript ...
<!DOCTYPE html>
<html>
<head></head>
<body>
<canvas id="mycanvas" width="400px" height="400px"></canvas>
<script>
window.onload = function(){
var canvas = document.getElementById('mycanvas');
var ctx = canvas.getContext('2d');
//cheat - use a hidden canvas
var hidden = document.createElement('canvas');
hidden.width = 400;
hidden.height = 400;
var hiddenCtx = hidden.getContext('2d');
hiddenCtx.strokeStyle = 'blue';
hiddenCtx.fillStyle = 'yellow';
hiddenCtx.lineWidth = 5;
//translate origin to centre of hidden canvas, and draw 3/4 of the image
hiddenCtx.translate(200,200);
for(var i=0; i<3; i++){
hiddenCtx.fillRect(-170, -150, 300, 120);
hiddenCtx.strokeRect(-170, -150, 300, 120);
hiddenCtx.rotate(90*(Math.PI/180));
}
//reset the hidden canvas to original status
hiddenCtx.rotate(90*(Math.PI/180));
hiddenCtx.translate(-200,-200);
//translate to middle of visible canvas
ctx.translate(200, 200);
//repeat trick, this time copying from hidden to visible canvas
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
ctx.rotate(180*(Math.PI/180));
ctx.drawImage(hidden, 200, 0, 200, 400, 0, -200, 200, 400);
};
</script>
</body>
</html>
Upvotes: 0
Reputation:
You cannot specify this behavior but you can implement an algorithmic-ish approach that uses composite modes.
As shown in this demo the result will be like this:
Define line width and the rectangles you want to draw (you can fill this array with the loop you already got to calculate the positions/angles - for simplicity I just use hard-coded ones here):
var lw = 4,
rects = [
[20, 15, 200, 75],
[150, 20, 75, 200],
[20, 150, 200, 75],
[15, 20, 75, 200]
], ...
I'll explain the line width below.
/// set line-width to half the size
ctx.lineWidth = lw * 0.5;
In the loop you add one criteria for the first draw which is also where you change composite mode. We also clear the canvas with the last rectangle:
/// loop through the array with rectangles
for(;r = rects[i]; i++) {
ctx.beginPath();
ctx.rect(r[0], r[1], r[2], r[3]);
ctx.fill();
ctx.stroke();
/// if first we do a clear with last rectangle and
/// then change composite mode and line width
if (i === 0) {
r = rects[rects.length - 1];
ctx.clearRect(r[0] - lw * 0.5, r[1] - lw * 0.5, r[2] + lw, r[3] + lw);
ctx.lineWidth = lw;
ctx.globalCompositeOperation = 'destination-over';
}
}
This will draw the rectangles and you have the flexibility to change the sizes without needing to recalculate clipping.
The line-width is set separately as stroke
strokes the line from the middle. Therefor, since we later use destination-over
mode it means half of the line won't be visible as we first fill which becomes part of destination so that the stroke will only be able to fill outside the stroked area (you could reverse the order of stroke
and fill
but will always run into an adjustment for the first rectangle).
We also need it to calculate the clipping which must include (half) the line on the outside.
This is also why we initially set it to half as the whole line will be drawn the first time - otherwise the first rectangle will have double as thick borders.
Upvotes: 6
Reputation: 25954
Sadly, the feature you want of setting z-indexes on part of an element using canvas is not available currently. If you just need it for the four rectangle object you could do something like this which hides part of the rectangle to fake the effect you want, however this is hard coded to only 4 rectangles.
var overlap_canvas = document.getElementById("overlap");
var overlap_context = overlap_canvas.getContext("2d");
var x = 200;
var y = x;
var rectQTY = 4 // Number of rectangles
overlap_context.translate(x, y);
for (j = 0; j < rectQTY; j++) { // Repeat for the number of rectangles
// Draw a rectangle
overlap_context.beginPath();
overlap_context.rect(-90, -100, 180, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.lineWidth = 7;
overlap_context.strokeStyle = 'blue';
overlap_context.stroke();
if (j === 3) {
overlap_context.beginPath();
overlap_context.rect(24, -86, 72, 80);
overlap_context.fillStyle = 'yellow';
overlap_context.fill();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20, -89.5);
overlap_context.lineTo(100, -89.5);
overlap_context.stroke();
overlap_context.closePath();
overlap_context.beginPath();
overlap_context.moveTo(20.5, -93.1);
overlap_context.lineTo(20.5, 23);
overlap_context.stroke();
overlap_context.closePath();
}
// Degrees to rotate for next position
overlap_context.rotate((Math.PI / 180) * 360 / rectQTY);
}
If you have to make it dynamic, you could cut the shapes like Dark Duck suggested or you could try to create a function that detects when an object is overlapped and redraw it one time per rectangle (hard to do and not sure if it'd work). Perhaps you could come up with some equation for positioning the elements in relation to how I have them hard coded now to always work depending on the rotation angle, this would be your best bet IMO, but I don't know how to make that happen exactly
Overall you can't really do what you're looking for at this point in time
Upvotes: 1
Reputation: 1777
The only way to do it to cut your rectangles and compute which sub rectangle goes over which one. But I think you will have to draw your borders and inner rectangles separately because separating rectangles will add additional borders.
Hope it helped
Upvotes: 1