I'd like to keep track of my stickman if I were to move it. However since my stickman is a bunch of lines and I believe the only way I can do this is by checking if certain pixels are of a certain color. Is there a better way of keep track of where my stickman is located on the canvas? I was told that if my stickman were an object my goal would be easier to reach. That said, I thought my stickman was of type object literal already. Any help would be appreciated thank you!
stickman = {head: [200, 200, 10,0, 2*Math.PI ],
body: [195, 210, 178, 250],
rightArm: [192,215,200,230,210,230],
leftArm: [192,215,178 ,222,178,230],
rightLeg: [178, 250,190,260,185,275,192, 275],
leftLeg: [178, 250, 168, 260, 155, 262,153, 268]
} ;
function costume1(){
context.strokeStyle = "rgb(0,0,0)";
context.beginPath();
//head
context.arc(stickman.head[0], stickman.head[1], stickman.head[2], stickman.head[3], stickman.head[4]);
//body
context.moveTo(stickman.body[0],stickman.body[1]);
context.lineTo(stickman.body[2],stickman.body[3]);
//right arm
context.moveTo(stickman.leftArm[0],stickman.leftArm[1]);
context.lineTo(stickman.leftArm[2] ,stickman.leftArm[3]);
context.lineTo(stickman.leftArm[4], stickman.leftArm[5]);
//left arm
context.moveTo(stickman.rightArm[0], stickman.rightArm[1]);
context.lineTo(stickman.rightArm[2], stickman.rightArm[3]);
context.lineTo(stickman.rightArm[4] , stickman.rightArm[5]);
//left leg
context.moveTo(stickman.rightLeg[0], stickman.rightLeg[1]);
context.lineTo(stickman.rightLeg[2],stickman.rightLeg[3]);
context.lineTo(stickman.rightLeg[4] , stickman.rightLeg[5]);
context.lineTo(stickman.rightLeg[6], stickman.rightLeg[7]);
//right leg
context.moveTo(stickman.leftLeg[0], stickman.leftLeg[1]);
context.lineTo(stickman.leftLeg[2], stickman.leftLeg[3]);
context.lineTo(stickman.leftLeg[4], stickman.leftLeg[5]);
context.lineTo(stickman.leftLeg[6] , stickman.leftLeg[7]);
context.stroke();
}
You are right - you have indeed created a stickman object. But if you want to move/track your stickman, it would be best to define the parts (the head, body, etc) in terms of a single, central point - for example you could use the centre of his head. Then to move/track the stickman, all you need to do is update those central points. The other parts of the stickman will then follow along.
Here is a demonstration of what I mean:
// set up a stickman, with a starting x and y
var Stickman = function(x, y) {
this.update(x, y);
}
// anytime you need to know the new positions for the
// stickman, call .update(newCenterX, newCenterY)
Stickman.prototype.update = function(x, y) {
this.centerX = x;
this.centerY = y;
this.head = [this.centerX, this.centerY, 10,0, 2*Math.PI ]
this.body = [
this.centerX-5,
this.centerY+10,
this.centerX-22,
this.centerY+50
]
this.rightArm = [ ];
this.leftArm = [ ];
// etc...
}
// here is how to make a new stickman
var man1 = new Stickman(200, 200);
// and move him!
console.log(man1.body);
man1.update(210, 200);
console.log(man1.body);`
Hope that helps! (I may not have got the offsets right :-) )
You typically use context.translate to move your fixed-coordinate stickman to a different position.
// move the context origin 100px rightward
context.translate(100,0);
// redraw the stickman (it will be 100px rightward of the original)
costume1();
But if you actually want a version of the stickman with your original fixed-coordinates changed to new "moved" fixed-coordinates, you can send your original stickman into a conversion function that changes the coordinates for you.
To make tracking any stickman easier, add an x: & y: property to every stickman that indicates how this stickman is offset-X & offset-Y from the original stickman.
Here's example code and a demo:
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
stickman = {
x:0,y:0,
head: [200, 200, 10,0, 2*Math.PI ],
body: [195, 210, 178, 250],
rightArm: [192,215,200,230,210,230],
leftArm: [192,215,178 ,222,178,230],
rightLeg: [178, 250,190,260,185,275,192, 275],
leftLeg: [178, 250, 168, 260, 155, 262,153, 268]
} ;
// draw original stickman
costume1(stickman,'black');
// move the stickman's x,y
stickman.x=-50;
stickman.y=-50;
// get the coordinates of the translated stickman
var stickman1={ x:stickman.x, y:stickman.y };
translateStickman(stickman,stickman1);
// draw the moved stickman1
costume1(stickman1,'red');
function costume1(stickman,strokecolor){
// move the canvas origin to the stickman's x,y
context.translate(stickman.x,stickman.y);
context.strokeStyle = strokecolor;
context.beginPath();
//head
context.arc(stickman.head[0], stickman.head[1], stickman.head[2], stickman.head[3], stickman.head[4]);
//body
context.moveTo(stickman.body[0],stickman.body[1]);
context.lineTo(stickman.body[2],stickman.body[3]);
//right arm
context.moveTo(stickman.leftArm[0],stickman.leftArm[1]);
context.lineTo(stickman.leftArm[2] ,stickman.leftArm[3]);
context.lineTo(stickman.leftArm[4], stickman.leftArm[5]);
//left arm
context.moveTo(stickman.rightArm[0], stickman.rightArm[1]);
context.lineTo(stickman.rightArm[2], stickman.rightArm[3]);
context.lineTo(stickman.rightArm[4] , stickman.rightArm[5]);
//left leg
context.moveTo(stickman.rightLeg[0], stickman.rightLeg[1]);
context.lineTo(stickman.rightLeg[2],stickman.rightLeg[3]);
context.lineTo(stickman.rightLeg[4] , stickman.rightLeg[5]);
context.lineTo(stickman.rightLeg[6], stickman.rightLeg[7]);
//right leg
context.moveTo(stickman.leftLeg[0], stickman.leftLeg[1]);
context.lineTo(stickman.leftLeg[2], stickman.leftLeg[3]);
context.lineTo(stickman.leftLeg[4], stickman.leftLeg[5]);
context.lineTo(stickman.leftLeg[6] , stickman.leftLeg[7]);
context.stroke();
// always clean up, unto the last translate
// == move the canvas origin back to 0,0
context.translate(-stickman.x,-stickman.y);
}
// create a new stickman with moved coordinates
function translateStickman(stickman,trxStickman){
var x=stickman1.x;
var y=stickman1.y;
var translate=function(a){
for(var i=0;i<a.length;i+=2){
a[i]+=x;
a[i+1]+=y;
}
}
trxStickman.head=stickman.head.slice();
trxStickman.body=stickman.body.slice();
trxStickman.rightArm=stickman.rightArm.slice();
trxStickman.leftArm=stickman.leftArm.slice();
trxStickman.rightLeg=stickman.rightLeg.slice();
trxStickman.leftLeg=stickman.leftLeg.slice();
trxStickman.head[0]+=x;
trxStickman.head[1]+=y;
translate(trxStickman.body);
translate(trxStickman.rightArm);
translate(trxStickman.leftArm);
translate(trxStickman.rightLeg);
translate(trxStickman.leftLeg);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<h4>Black == original stickman, Red == moved stickman</h4>
<canvas id="canvas" width=300 height=300></canvas>
Related
I'm trying to draw some kind of a "ray" with Phaser 3.
I started the experiment with a simple rectangle to represent the ray. Here is the code.
this.add.circle(90, 290, 10, 0xf00000);
this.add.circle(290, 190, 10, 0xf00000);
let angle = Phaser.Math.Angle.Between(90, 290, 290, 190)
let reference = this.add.rectangle(90, 290, 600, 5, 0x00f000).setOrigin(0);
reference.rotation = angle
The line doesn't start at the center of its starting point.
I know the reason is Phaser draws the line (actually rectangle) starting top-left 90, 290 where the centerline of the rectangle is supposed to start at.
To fix it, I just need to change y of the top-left
let reference = this.add.rectangle(90, 290-5/2, 600, 5, 0x00f000).setOrigin(0);
where the 5 in 5/2 represents the height of the green rectangle.
And then I got what I want
However, things got complicated when I try to move something along the "ray".
Here is the code.
var game = new Phaser.Game({
scene: {
preload: preload,
create: create
}
});
function preload() {
this.load.path = 'https://raw.githubusercontent.com/liyi93319/phaser3_rpg/main/part1/assets/';
this.load.atlas('bolt', 'bolt_atlas.png', 'bolt_atlas.json');
}
function create() {
this.add.circle(90, 290, 10, 0xf00000);
this.add.circle(290, 190, 10, 0xf00000);
let angle = Phaser.Math.Angle.Between(90, 290, 290, 190)
let reference = this.add.rectangle(90, 290 - 5 / 2, 600, 5, 0x00f000).setOrigin(0);
reference.rotation = angle
let bolt = this.add.sprite(90, 290-76/2, 'bolt', 'bolt_strike_0002').setOrigin(0);
bolt.rotation = angle;
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
I just added 2 lines of code based on the previously working code, with the same idea
let bolt = this.add.sprite(90, 290-76/2, 'bolt', 'bolt_strike_0002').setOrigin(0);
bolt.rotation = angle;
the 76 in 76/2 represents the height of the bolt (image/rectangle) I put.
290 represents the y coordinate of the center of the starting point.
The exactly same idea puts the green rectangle along the centerline of it though cannot put the bolt in the right place (along the centerline of the green rectangle), why is that? What am I missing?
To position the Image / rectangle, ... phaser uses the origin.
Fastes way to solve your problem is to use setOrigin(0, .5)
(Details to Phasers Origin )
Here The parameters explained:
the first paramater is the x - value: 0 = 0% = left
the second paramater is the y - value: 0.5 = 50% = middle
Info: If you set only one, like setOrigin(0), both x and y are set to 0.
Here the updated example:
var game = new Phaser.Game({
scene: {
preload: preload,
create: create
}
});
function preload() {
this.load.path = 'https://raw.githubusercontent.com/liyi93319/phaser3_rpg/main/part1/assets/';
this.load.atlas('bolt', 'bolt_atlas.png', 'bolt_atlas.json');
}
function create() {
this.add.circle(90, 290, 10, 0xf00000);
this.add.circle(290, 190, 10, 0xf00000);
let angle = Phaser.Math.Angle.Between(90, 290, 290, 190)
let reference = this.add.rectangle(90, 290 , 600, 5, 0x00f000).setOrigin(0, .5);
reference.rotation = angle
let bolt = this.add.sprite(90, 290, 'bolt', 'bolt_strike_0002').setOrigin(0, .5);
bolt.rotation = angle;
}
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
Info: In this Answer: https://stackoverflow.com/a/70803036/1679286, you can see how a different set origin alteres the effect of the scale property.
You need an offset which with change its position in local coordinates and the medium of any object is (width/2,height/2) given origin is at top left.
I am trying to plot some points on canvas through x and y coordinates. First codepen I have created does not plot all points, I think it has scale issue. But can not figure out how to set proper scale.
Here in my second codepen all points are plotted very closely. The points plotted creates a text hello zap
Here in this jsfiddle I have plotted all points through scatter chart. Please refer codepen and fiddle for all data.Can anyone please suggest me a proper way to plot this points properly.
Thank You.
//html code
<canvas id="canvas"></canvas>
//js code
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
width = canvas.width = 800,
height = canvas.height = 400;
// var stats = [40, 65, 72, 120, 250, 87, 100, 42];
var stats = [
{
"x": 54.75,
"y": 71,
"dotType": 17,
"pressure": 19,
"timestamp": 1535708931610
},
{
"x": 54.7599983215332,
"y": 71,
"dotType": 18,
"pressure": 27,
"timestamp": 1535708931619
}
]
context.translate(0, height);
context.scale(1, -1);
context.fillStyle = '#f6f6f6';
context.fillRect(0, 0, width, height);
var left = 0,
prev_stat = stats[0].y,
move_left_by = 100;
for(stat in stats) {
the_stat = stats[stat].y;
console.log(left, prev_stat);
console.log(left+move_left_by, the_stat)
context.beginPath();
context.arc(left+move_left_by, the_stat,1, 0, Math.PI * 2, true);
context.stroke();
prev_stat = the_stat;
left += move_left_by
}
Edit:
This data is from a neoPen, whenever I write something on a paper with that pen on A4 size paper it send me coordinates of that page.This points are near because just a text written on a page.
I am looking at your second codepen.
First you have the data string that you are breaking into points and you put those points in the canvasPts array.
Next you are redeclaring the points of the canvasPts, deleting all the pervious points.
Furthermore: the points of the canvasPts are all almost in the same spot. Please take a look at the values for the x and y.
And this is not all. You are dividing the values for x and y coordinates by 100, making them even nearer.
You do not declare a size for your canvas, making your canvas of 300/150 px.
Supposing I would try to draw the SVG path for your data, this appears to be a group of lines with a length of 0. There is nothing to draw.
Please edit your question explaining how did you get your data.
UPDATE:
In order to avoid cluttering I've put your data in an external file.
I'm doing it 2 ways:
first in svg: SVG is easily scalable, and was easier for me tu understand what happens. The vewBox for the svg is viewBox="53.5 68 12 5" which means that the svg canvas begins at x=53.5, y=68. The width of the svg canvas is 68 and the height is 5.
In Canvas-HTML5: I'm scaling the context 10 times since otherwise it would be extrmely small: ctx.scale(10,10); In order to achieve the same result in canvas and since I'm translating the context ctx.translate(-53.5, -68.0);
//SVG
let d=`M${data[0].x},${data[0].y}L`
for(let i = 1; i < data.length; i++){
d += `${data[i].x},${data[i].y} `
}
test.setAttributeNS(null, "d", d);
//canvas//////
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 120;
let ch = canvas.height = 50;
ctx.lineWidth = .1;
ctx.strokeStyle = "black";
ctx.scale(10,10);
ctx.translate(-53.5, -68.0);
ctx.beginPath();
ctx.moveTo(data[0].x,data[0].y);
for(let i = 1; i < data.length; i++){
ctx.lineTo(data[i].x,data[i].y);
}
ctx.stroke();
svg,canvas{border:1px solid}
path{fill: none; stroke:black; stroke-width:.05}
<svg viewBox="53.5 68 12 5">
<path id="test" />
</svg>
<canvas></canvas>
<script src='https://codepen.io/enxaneta/pen/dd442277a45b6cf1b5cc690200cdb3cf.js'></script>
I'm looking for a way to lines with markers in a <canvas> element, like the picture bellow.
In SVG we can do that thanks the markers but I haven't found a similar way to do that with canvas. I know that we can create patterns in canvas thanks to the createPattern function but I doubt it could help to solve the issue.
EDIT: This is not a duplicate of this question since I'm looking for a way to repeat a shape/marker on a path. It's not about placing marker at a specific given point.
As I've commented bellow, I've discovered the svg-path-properties which is almost perfect for my solution.
There is unfortunately no native way to add markers to strokes in the canvas API. Even if we are able to set strokeStyle to a CanvasPattern, there is no way to make this pattern follow our path's direction.
This means that you'll have to make the calculations of the direction and position of your markers yourself...
For this, you can refer to many posts, like this one proposed by Tomàs Antunes in comments.
However, to do the exact triangle shape you shown to us, I've got an non-mathy hack, which doesn't work very well, but that I'd like to share anyway.
Strokes can have dash-arrays, which will create gaps in the stroke.
By offsetting multiple times this dash-array, and decreasing the lineWidth of our stroke, we can sort-of create these arrow shapes :
var ctx = c.getContext('2d');
// our default path
ctx.beginPath();
ctx.moveTo(50, 20);
ctx.bezierCurveTo(230, 30, 150, 60, 50, 100);
ctx.bezierCurveTo(250, 50, 150, 60, 150, 150);
ctx.stroke();
// the hack
for (var i = 0; i < 9; i += .5) {
// set an dasharray of 1px visible, then 40 invisible
ctx.setLineDash([1, 49]);
// offset the dash-array by one px
ctx.lineDashOffset = i;
// reduce the width of our stroke
ctx.lineWidth = i;
ctx.stroke();
}
<canvas id="c"></canvas>
But the main caveat of this method (apart from requiring to redraw the same path 20 times) is that it will really follow the path, and that in angles, it may not completely look like our triangle shape anymore.
var ctx = c.getContext('2d');
// our default path
ctx.beginPath();
ctx.moveTo(50, 20);
ctx.bezierCurveTo(230, 30, 150, 60, 50, 100);
ctx.stroke();
// the hack
for (var i = 0; i < 9; i += .5) {
// set an dasharray of 1px visible, then 20 invisible
ctx.setLineDash([1, 14]);
// offset the dash-array by one px
ctx.lineDashOffset = i;
// reduce the width of our stroke
ctx.lineWidth = i*2;
ctx.stroke();
}
<canvas id="c"></canvas>
I have the following code to draw shapes (mainly used for rectangles) but the HTML5 drawing functions seem to draw borders with their thickness centered on the lines specified. I would like to have a border outside the surface of the shape and I'm at a loss.
Path.prototype.trace = function(elem, closePath) {
sd.context.beginPath();
sd.context.moveTo(this.getStretchedX(0, elem.width), this.getStretchedY(0, elem.height));
sd.context.lineCap = "square";
for(var i=1; i<this.points.length; ++i) {
sd.context.lineTo(this.getStretchedX(i, elem.width), this.getStretchedY(i, elem.height));
}
if(closePath) {
sd.context.lineTo(this.getStretchedX(0, elem.width), this.getStretchedY(0, elem.height));
}
}
getStrechedX and getStretchedY return the coordinates of the nth vertex once the shape is applied to a set element width, height and offset position.
Thanks to Ken Fyrstenberg's answer I've got it working for a rectangle, but this solution can sadly not apply to other shapes.
http://jsfiddle.net/0zq9mrch/
Here I drew two "wide" borders, one subtracting half the lineWidth to every position, another one adding. It doesn't work (as expected) because it's only going to put the thick lines above and to the left in one case, under and to the right in another - not "outside" the shape. You can also see a white area around the slope.
I tried working out how I could get the vertices to manually draw the path for the thick border (using fill() instead of stroke()).
But it turns out I still end up with the same problem: how to programatically determine if an edge is inside or outside. This would require some trigonometry and a heavy algorithm. For the purpose of my current work, this is too much trouble. I wanted to use this to draw a map of a building. The room walls need to be drawn outside the given dimensions, but I'll stick to standalone sloped walls for now.
Solution
You can solve this by drawing two lines:
First line with line thickness as intended
Second line contracted with 50% of the outer line width
To contract, add 50% to x and y, subtract line-width (or 2x 50%) from width and height.
Example
var ctx = document.querySelector("canvas").getContext("2d");
var lineWidth = 20;
var lw50 = lineWidth * 0.5;
// outer line
ctx.lineWidth = lineWidth; // intended line width
ctx.strokeStyle = "#975"; // color for main line
ctx.strokeRect(40, 40, 100, 100); // full line
// inner line
ctx.lineWidth = 2; // inner line width
ctx.strokeStyle = "#000"; // color for inner line
ctx.strokeRect(40 + lw50, 40 + lw50, 100 - lineWidth, 100 - lineWidth);
<canvas></canvas>
Complex shapes
For more complex shapes you will have to calculate the path manually. This is a little bit more complex and perhaps too broad for SO. You have to consider things like tangents, angle at bends, intersections and so forth.
One way to "cheat" is to:
draw the main line at full thickness to canvas
then use reuse the path as a clipping mask
change composite mode to destination-atop
draw the shape offset in various direction
restore clipping
change color and reuse path again for the main line.
The offset value below will determine the thickness of the inner line while the directions will determine resolution.
var ctx = document.querySelector("canvas").getContext("2d");
var lineWidth = 20;
var offset = 0.5; // line "thickness"
var directions = 8; // increase to increase details
var angleStep = 2 * Math.PI / 8;
// shape
ctx.lineWidth = lineWidth; // intended line width
ctx.strokeStyle = "#000"; // color for inner line
ctx.moveTo(50, 100); // some random shape
ctx.lineTo(100, 20);
ctx.lineTo(200, 100);
ctx.lineTo(300, 100);
ctx.lineTo(200, 200);
ctx.lineTo(50, 100);
ctx.closePath();
ctx.stroke();
ctx.save()
ctx.clip(); // set as clipping mask
ctx.globalCompositeOperation = "destination-atop"; // draws "behind" existing drawings
for(var a = 0; a < Math.PI * 2; a += angleStep) {
ctx.setTransform(1,0,0,1, offset * Math.cos(a), offset * Math.sin(a));
ctx.drawImage(ctx.canvas, 0, 0);
}
ctx.restore(); // removes clipping, comp. mode, transforms
// set new color and redraw same path as previous
ctx.strokeStyle = "#975"; // color for inner line
ctx.stroke();
<canvas height=250></canvas>
I'm late to the party, but here's an alternate way to "outside stroke" a complex path.
It uses a PathObject to simplify the process of creating the outside stroke.
The PathObject saves all the commands and arguments used to define your complex path.
This PathObject can also replay the commands--and can thereby redefine/redraw the saved path.
The PathObject class is re-usable. You can use it to save any path (simple or complex) that you need to redraw.
Html5 Canvas will soon have its own Path2D object built into the context, but my example below has a cross-browser polyfill that can be used until the Path2D object is implemented.
An illustration of a cloud with a silver lining applied using an outside stroke.
"Here's how it's done..."
Create a PathObject that can save all the commands and arguments used to define your complex path. This PathObject can also replay the commands--and can thereby redefine the saved path. Html5 Canvas will soon have its own Path2D object built into the context, but my example below is a cross-browser polyfill that can be used until the Path2D object is implemented.
Save a complex path using the PathObject.
Play the path commands on the main canvas and fill/stroke as desired.
Play the path commands on a temporary in-memory canvas.
On the temporary canvas:
Set a context.lineWidth of twice your desired outside stroke width and do the stroke.
Set globalCompositeOperation='destination-out' and fill. This will cause the inside of the complex path to be cleared and made transparent.
Draw the temporary canvas onto the main canvas. This causes your existing complex path on the main canvas to get the "outside stroke" from the in-memory canvas.
Here's example code and a Demo:
function log(){console.log.apply(console,arguments);}
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");
// A "class" that remembers (and can replay) all the
// commands & arguments used to define a context path
var PathObject=( function(){
// Path-related context methods that don't return a value
var methods = ['arc','beginPath','bezierCurveTo','clip','closePath',
'lineTo','moveTo','quadraticCurveTo','rect','restore','rotate',
'save','scale','setTransform','transform','translate','arcTo'];
var commands=[];
var args=[];
function PathObject(){
// add methods plus logging
for (var i=0;i<methods.length;i++){
var m = methods[i];
this[m] = (function(m){
return function () {
if(m=='beginPath'){
commands.length=0;
args.length=0;
}
commands.push(m);
args.push(arguments);
return(this);
};}(m));
}
};
// define/redefine the path by issuing all the saved
// path commands to the specified context
PathObject.prototype.definePath=function(context){
for(var i=0;i<commands.length;i++){
context[commands[i]].apply(context, args[i]);
}
}
//
PathObject.prototype.show=function(){
for(var i=0;i<commands.length;i++){
log(commands[i],args[i]);
}
}
//
return(PathObject);
})();
var x=75;
var y=100;
var scale=0.50;
// define a cloud path
var path=new PathObject()
.beginPath()
.save()
.translate(x,y)
.scale(scale,scale)
.moveTo(0, 0)
.bezierCurveTo(-40, 20, -40, 70, 60, 70)
.bezierCurveTo(80, 100, 150, 100, 170, 70)
.bezierCurveTo(250, 70, 250, 40, 220, 20)
.bezierCurveTo(260, -40, 200, -50, 170, -30)
.bezierCurveTo(150, -75, 80, -60, 80, -30)
.bezierCurveTo(30, -75, -20, -60, 0, 0)
.restore();
// fill the blue sky on the main canvas
ctx.fillStyle='skyblue';
ctx.fillRect(0,0,canvas.width,canvas.height);
// draw the cloud on the main canvas
path.definePath(ctx);
ctx.fillStyle='white';
ctx.fill();
ctx.strokeStyle='black';
ctx.lineWidth=2;
ctx.stroke();
// draw the cloud's silver lining on the temp canvas
path.definePath(ctx1);
ctx1.lineWidth=20;
ctx1.strokeStyle='silver';
ctx1.stroke();
ctx1.globalCompositeOperation='destination-out';
ctx1.fill();
// draw the silver lining onto the main canvas
ctx.drawImage(canvas1,0,0);
body{ background-color: ivory; }
canvas{border:1px solid red;}
<h4>Main canvas with original white cloud + small black stroke<br>The "outside silver lining" is from the temp canvas</h4>
<canvas id="canvas" width=300 height=300></canvas>
<h4>Temporary canvas used to create the "outside stroke"</h4>
<canvas id="canvas1" width=300 height=300></canvas>
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!
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.
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
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);
}
Demo here
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
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>
Demo on jsFiddle