JavaScript canvas clipping shape when out of bounds - javascript

What I'm asking for may be extremely easy, but I've been having quite a bit of trouble getting the intended result.
I want a shape (in this example it's squares but should work with other shapes such as circles, etc) to cut itself off when it leaves the bounds of another shape.
Basically, top image is what I want, bottom is what I currently have:
http://imgur.com/a/oQkzG
I heard this can be done with globalCompositeOperation, but am looking for any solution that will give the wanted result.
This is the current code, if you can't use JSFiddle:
// Fill the background
ctx.fillStyle = '#0A2E36';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Fill the parent rect
ctx.fillStyle = '#CCA43B';
ctx.fillRect(100, 100, 150, 150);
// Fill the child rect
ctx.fillStyle = 'red';
ctx.fillRect(200, 200, 70, 70);
// And fill a rect that should not be affected
ctx.fillStyle = 'green';
ctx.fillRect(80, 80, 50, 50);
JSFiddle Link

Since you need some kind of relation between objects - a scene graph -, you should build it now.
From your question, it seems that any child element should be drawn clipped by its parent element.
(Yes composite operation could come to the rescue, but they are handy only when drawing like 2 figures on a cleared background, things get quickly complicated otherwise, and you might have to use a back canvas, so clipping is simpler here.)
I did below a most basic class that handles the rect case, you'll see that it isn't very difficult to build.
The 'scene' is made out of a background Rect, which has two childs, the yellow and the green. And the yellow Rect has a red child.
var canvas = document.getElementById('cv');
var ctx = canvas.getContext('2d');
function Rect(fill, x, y, w, h) {
var childs = [];
this.draw = function () {
ctx.save();
ctx.beginPath();
ctx.fillStyle = fill;
ctx.rect(x, y, w, h);
ctx.fill();
ctx.clip();
for (var i = 0; i < childs.length; i++) {
childs[i].draw();
}
ctx.restore();
}
this.addChild = function (child) {
childs.push(child);
}
this.setPos = function (nx, ny) {
x = nx;
y = ny;
}
}
// background
var bgRect = new Rect('#0A2E36', 0, 0, canvas.width, canvas.height);
// One parent rect
var parentRect = new Rect('#CCA43B', 100, 100, 150, 150);
// child rect
var childRect = new Rect('red', 200, 200, 70, 70);
parentRect.addChild(childRect);
// Another top level rect
var otherRect = new Rect('green', 80, 80, 50, 50);
bgRect.addChild(parentRect);
bgRect.addChild(otherRect);
function drawScene() {
bgRect.draw();
drawTitle();
}
drawScene();
window.addEventListener('mousemove', function (e) {
var rect = canvas.getBoundingClientRect();
var x = (e.clientX - rect.left);
var y = (e.clientY - rect.top);
childRect.setPos(x, y);
drawScene();
});
function drawTitle() {
ctx.fillStyle = '#DDF';
ctx.font = '14px Futura';
ctx.fillText('Move the mouse around.', 20, 24);
}
<canvas id='cv' width=440 height=440></canvas>

Related

How to make only two shapes compost together in HTML canvas without effecting other shapes

So my goal is simple... I think. I want to draw a rectangle with a hole cut out in the middle. But I don't want that hole to go through anything else on the canvas.
Right now I have something like this:
context.fillStyle = 'blue';
context.fillRect(0, 0, width, height);
context.fillStyle = "#000000";
context.beginPath();
context.rect(0, 0 , width, height);
context.fill();
enter code here
context.globalCompositeOperation="destination-out";
context.beginPath();
context.arc(width / 2, height / 2 , 80, 0, 50);
context.fill();
But this also cuts through the background as well, how would I make it only cut through the black rectangle and nothing else?
Visual Example in case I'm not explaining well:
Take a look at this see if that is what you are looking for:
<canvas id="canvas" width="200" height="160"></canvas>
<script>
class Shape {
constructor(x, y, width, height, color) {
this.color = color
this.path = new Path2D();
this.path.arc(x, y, height / 3, 0, 2 * Math.PI);
this.path.rect(x + width / 2, y - height / 2, -width, height);
}
draw(ctx) {
ctx.beginPath();
ctx.fillStyle = this.color;
ctx.fill(this.path);
}
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
shapes = [];
shapes.push(new Shape(40, 40, 80, 80, "blue"));
shapes.push(new Shape(60, 65, 70, 70, "red"));
shapes.push(new Shape(80, 40, 65, 65, "gray"));
shapes.forEach((s) => {
s.draw(ctx);
});
</script>
I'm using a Path2D() because I had a quick example from something I did before, but that should be doable without it too. The theory behind is simple, we just want to fill the opposite on the circle.
I hard coded the radius to a third of the height, but you can change all that to something else or even be a parameter the final user can change
Is this what you were hoping to achieve?
const context = document.getElementById("canvas").getContext('2d');
const width = 100;
const height = 100;
const circleSize = 30;
context.fillStyle = "black";
context.fillRect(0, 0 , width, height);
context.beginPath();
context.arc(width / 2, height / 2, circleSize, 0, 2 * Math.PI);
context.clip();
context.fillStyle = "blue";
context.fillRect(0, 0, width, height);
<canvas id="canvas"/>
This article might be useful:
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Compositing
In the example above, I'm using clip.
As the documentation states:
The CanvasRenderingContext2D.clip() method of the Canvas 2D API turns the current or given path into the current clipping region.
This means that any element added to the context after this line, will only be drawn within that clipped path.

How to redraw a rectangle in canvas?

In a canvas, I have drawn two rectangles. In button click event, I want to redraw one rectangle out of these two rectangles I have drawn. How can I achieve this?
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
ctx.rect(10, 20, 150, 100);
ctx.fill();
ctx.rect(200, 20, 100, 100);
ctx.stroke();
change = () => {
//need to change height of second rectangle
alert('size changed')
}
jsFiddle link
In the above fiddle link, I want to change the height of the second rectangle . Can anyone suggest ideas to achieve thii?
Thanks in advance,
You need to redraw the canvas - i.e. redraw the background and the two rectangles, something like this:
ctx.fillRect(0, 0, canvas.width, canvas.height);
//change the size of one rectangle, then redraw both of them
ctx.rect(10, 20, 150, 100);
ctx.fill();
ctx.rect(200, 20, 200, 150);
ctx.stroke();
Another way would be to cover only the second rectangle with a white rectangle (or whatever background color you have), and redraw this second rectangle:
//draw first rectangle
ctx.rect(10, 20, 150, 100);
//cover old rectangle using a white rect
ctx.fillStyle = "white";
ctx.fill();
ctx.rect(200, 20, 100, 100);
//redraw second rectangle
ctx.fillStyle = "black";
ctx.fill();
ctx.rect(200, 20, 200, 150);
ctx.stroke();
You need to save the rectangle information and redraw them on each change.
This is because canvas doesn't store information as objects, but merely commits pixels.
//Setup canvas
var canvas = document.body.appendChild(document.createElement("canvas"));
var ctx = canvas.getContext('2d');
canvas.height = 200;
canvas.width = 400;
ctx.fillStyle = "red";
//Setup rectangles as objects
var rect1 = { x: 10, y: 20, w: 150, h: 100 };
var rect2 = { x: 200, y: 20, w: 100, h: 100 };
//Setup controls
var table = document.body.appendChild(document.createElement("table"));
var tr = table.appendChild(document.createElement("tr"));
tr.appendChild(document.createElement("td")).innerHTML = "X-position:";
var xInput = tr.appendChild(document.createElement("input"));
xInput.type = "number";
xInput.value = rect2.x.toString();
tr = table.appendChild(document.createElement("tr"));
tr.appendChild(document.createElement("td")).innerHTML = "Y-position:";
var yInput = tr.appendChild(document.createElement("input"));
yInput.type = "number";
yInput.value = rect2.y.toString();
tr = table.appendChild(document.createElement("tr"));
tr.appendChild(document.createElement("td")).innerHTML = "Width:";
var wInput = tr.appendChild(document.createElement("input"));
wInput.type = "number";
wInput.value = rect2.w.toString();
tr = table.appendChild(document.createElement("tr"));
tr.appendChild(document.createElement("td")).innerHTML = "Height:";
var hInput = tr.appendChild(document.createElement("input"));
hInput.type = "number";
hInput.value = rect2.h.toString();
//Draw function
function change() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
[rect1, rect2].forEach(function (rect) {
ctx.fillRect(rect.x, rect.y, rect.w, rect.h);
});
}
//initial draw
change();
//Redraw event
function redraw() {
rect2.x = parseInt(xInput.value, 10);
rect2.y = parseInt(yInput.value, 10);
rect2.w = parseInt(wInput.value, 10);
rect2.h = parseInt(hInput.value, 10);
change();
}
//Bind events
[xInput, yInput, wInput, hInput].forEach(function (input) {
input.addEventListener("change", redraw);
input.addEventListener("keyup", redraw);
});

Javascript canvas: Set own color for two different objects (squares)

I'm real frustrated with this problem.
So, I have following simple code:
var canvas = document.getElementById('canvasField');
var ctx = canvas.getContext('2d');
function Square(x, y, sizeOfSide) {
this.draw = function () {
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x + sizeOfSide, y);
ctx.lineTo(x + sizeOfSide, y + sizeOfSide);
ctx.lineTo(x, y + sizeOfSide);
ctx.lineTo(x,y);
ctx.closePath();
ctx.stroke();
}
this.setColor = function (color) {
ctx.fillStyle = color;
ctx.fill();
}
}
Square is my object. I can draw square and probably I can set fill-color for it. So next code works fine.
var square1 = new Square(100, 100, 100);
var square2 = new Square(250, 200, 100);
square1.draw();
square1.setColor('green');
square2.draw();
square2.setColor('yellow');
https://i.stack.imgur.com/gz50K.png
But If I change it to this:
var square1 = new Square(100, 100, 100);
var square2 = new Square(250, 200, 100);
square1.draw();
square2.draw();
square1.setColor('green');
square2.setColor('yellow');
it breaks down:
https://i.stack.imgur.com/Qeojl.png
It seems to me that I understand the reason. Two objects have the same context. And square2 sets color yellow for context and square1 loses his color. Maybe I am not right. I expected that they will be two independent objects and I'll be able to manipulate their conditions at any place in the code. I have no idea what to do next. Please help!
DEMO
var canvas = document.getElementById('canvasField');
var ctx = canvas.getContext('2d');
function Square(x, y, sizeOfSide) {
this.draw = function () {
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x + sizeOfSide, y);
ctx.lineTo(x + sizeOfSide, y + sizeOfSide);
ctx.lineTo(x, y + sizeOfSide);
ctx.lineTo(x,y);
ctx.closePath();
ctx.stroke();
}
this.setColor = function (color) {
this.draw();
ctx.fillStyle = color;
ctx.fill();
}
}
//var square1 = new Square(100, 100, 100);
//var square2 = new Square(250, 200, 100);
//square1.draw();
//square1.setColor('green');
//square2.draw();
//square2.setColor('yellow');
var square1 = new Square(100, 100, 100);
var square2 = new Square(250, 200, 100);
//square1.draw();
//square2.draw();
square1.setColor('green');
square2.setColor('yellow');
canvas {
border : 2px dotted blue;
}
<canvas id='canvasField' width=500 height=500></canvas>
Call the draw function of object inside setColor function. So it will draw the square first then will fill it using given color.
Most of time when you want to change something on a canvas, you have to draw it again. Your first block of code is good, if you want to change color of a square, use setColor then draw it again.

How can I stop HTML5 Canvas Ghosting?

I made a small program that:
changes the mouse cursor inside the canvas to a black square
gives the black square a nice trail that fades away over time (the point of the program)
Here's the code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.save();
ctx.globalAlpha = 0.1; // the opacity (i.e. fade) being applied to the canvas on each function re-run
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
ctx.fillStyle = "black";
ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
Here's a live example: https://jsfiddle.net/L6j71crw/2/
Problem
However the trail does not fade away completely, and leaves a ghosting trail.
Q: How can I remove the ghosting trail?
I have tried using clearRect() in different ways, but it just clears the entire animation leaving nothing to display. At best it just removes the trail and only fades the square cursor alone, but it still doesn't make the cursor completely transparent when the fading process is completed. I have tried finding posts about it, but I found nothing that gave a definitive answer and—most importantly—no posts with a working example.
Any ideas?
Try having a list of positions, this won't leave a ghost trail!
my code:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var Positions = [];
var maxlength = 20;
canvas.style.cursor = 'none'; // remove regular cursor inside canvas
var V2 = function(x, y){this.x = x; this.y = y;};
function getMousePos(canvas, e) {
// ctx.clearRect(0, 0, canvas.width, canvas.height);
var rect = canvas.getBoundingClientRect();
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
};
}
function fadeCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var e = 0; e != Positions.length; e++)
{
ctx.fillStyle = ctx.fillStyle = "rgba(0, 0, 0, " + 1 / e + ")";
ctx.fillRect(Positions[e].x, Positions[e].y, 8, 8);
}
if(Positions.length > 1)
Positions.pop()
//ctx.save();
//ctx.globalAlpha = 0.5; // the opacity (i.e. fade) being applied to the canvas on each function re-run
//ctx.fillStyle = "#fff";
//ctx.fillRect(0, 0, canvas.width, canvas.height); // area being faded (whole canvas)
//ctx.restore();
requestAnimationFrame(fadeCanvas); // animate at 60 fps
}
fadeCanvas();
function draw(e) {
var pos = getMousePos(canvas, e);
Positions.unshift(new V2(pos.x, pos.y));
if(Positions.length > maxlength)
Positions.pop()
//ctx.fillStyle = "black";
//ctx.fillRect(pos.x, pos.y, 8, 8); // the new cursor
}
addEventListener('mousemove', draw, false);
JSFiddle: https://jsfiddle.net/L6j71crw/9/
Edit: made the cursor constant.

How to use Prototypal Pattern to make an html5 rectangle for reuse

I'm trying to understand Prototypal Inheritance using the Prototypal pattern by making a rectangle object and an instance of the rectangle. Seems easy, but I'm not getting it. The RectanglePrototype's method is not drawing the rectangle onto the canvas. If I use the same function as the method it works. Where am I going wrong? Also, I understand that I will need to make an initialization function, but I'm thinking I can do that later after I get the first basic steps down.
javascript:
window.onload = function () {
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var RectanglePrototype = {
// Properties
x: 0,
y: 0,
width: 100,
height: 100,
color: "white",
// Method
get:function (x, y, width, height, color) {
context.translate(0 , 0);
context.beginPath();
context.rect(x, y, width, height);
context.fillStyle = color;
context.fill();
return this.x, this.y, this.width, this.height, this.color;
}
};
console.log(RectanglePrototype.get);
// Instance of RectanglePrototype
var rectangle1 = Object.create(RectanglePrototype);
rectangle1.x = 200;
rectangle1.y = 100;
rectangle1.width = 300;
rectangle1.height = 150;
rectangle1.color = '#DBE89B';
// Draw Rectangle Function
function rect(x, y, width, height, color) {
context.translate(0 , 0);
context.beginPath();
context.rect(x, y, width, height); // yLoc-canvas.height = -300
context.fillStyle = color;
context.fill();
};
rect(0, 450, 50, 50, '#F7F694');
}
</script>
Prototypes are extensions of objects that result from a constructor. Method lookups go through the object properties before looking into prototype.
I proper JS design, you would only add the non-function properties in your constructor.
//Your constructor
function Rectangle(){
// Properties
this.x = 0;
this.y = 0;
this.width = 100;
this.height = 100;
this.color = 'red';
}
And then put the methods in your prototype:
//I prefer the term 'draw'
Rectangle.prototype.draw = function(ctx){
ctx.save();
ctx.beginPath();
ctx.rect(this.x, this.y, this.width, this.height);
ctx.fillStyle = this.color;
ctx.fill();
ctx.restore();
};
Then, to use in your project:
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
//List of your shapes to draw on the canvas
var shapes = [];
//Instance of Rectangle
var rectangle1 = new Rectangle();
rectangle1.x = 200;
rectangle1.y = 100;
rectangle1.width = 300;
rectangle1.height = 150;
rectangle1.color = '#DBE89B';
shapes.push(rectangle1);
//Draw your shapes
function draw(){
window.requestAnimationFrame(draw); //See MDN for proper usage, but always request next fram at the start of your draw loop!
for(var i = 0; i<shapes.length; i++){
shapes[i].draw(context);
}
}
This is the 'proper' way of drawing to the canvas. For anything larger scale, please look into existing engines that do a looooot of hard work for you and have thought of everything so you don't have to. I have worked on such engines.

Categories

Resources