I got a code that based on user's input, draws a rectangle. I need to create another rect within the first one as shown in the picture below.
https://imgur.com/zbMNXFv
I got the following code until now.
https://codepen.io/newtz/pen/MWWKRYG
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.strokeRect(zoomedX(50), zoomedY(50), zoomed(width), zoomed(height));
}
Each time you draw a rectangle, you can draw a smaller one inside.
For example, if the width and height equal 100, then you will call:
context.strokeRect(0, 0, 100, 100);
Then you can draw the smaller rectangle using the previous values:
context.strokeRect(0 + 10, 0 + 10, 100 - 20, 100 - 20);
To allow this, you have to give parameters to your draw() function, so can can make the position and size of the new rectangles vary:
draw() => draw(x, y, width, height)
In your code, declare two variables x and y and assign default values (50 in your code).
Then your draw() function:
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.strokeRect(zoomedX(50), zoomedY(50), zoomed(width), zoomed(height));
}
Change like this:
function draw(x, y, width, height) {
context.beginPath();
context.strokeRect(zoomedX(x), zoomedY(y), zoomed(width), zoomed(height));
}
Now it has parameters. We also need to remove the clearing instruction: this function is designed to draw, only this. Moreover, if you don't remove the clearing line, each drawing will remove previously drawn rectangles.
Finally, call the draw(...) functions in your callbacks:
$width.addEventListener("keyup", function () {
width = this.value;
context.clearRect(0, 0, canvas.width, canvas.height);
draw(x, y, width, height);
draw(x + 5, y + 5, width - 10, height - 10);
draw(x + 10, y + 10, width - 20, height - 20);
}, false);
Same for the height. So each time you update the width or the height value, it will draw 3 nested rectangles.
Here's a Fiddle: https://jsfiddle.net/nmerinian/jzeygapL/4/
For the test, I created a drawMultiple(x, y, width, height) function which draws three rectangles:
function drawMultiple(x, y, width, height) {
draw(x, y, width, height);
draw(x + 5, y + 5, width - 10, height - 10);
draw(x + 10, y + 10, width - 20, height - 20);
}
Related
I have two things that I want to display with p5, one is a 2D background and the other is a 3D WebGL foreground, both generated by p5. What I noticed is that even if I draw the 2D background before the 3D stuff in the draw() function, the 3D stuff will still be partially covered by the background when rotateX() or rotateY() is called. It looks kind of like this:
I suspect what's happening is that the 2d and 3d stuff are both on the same z-plane, therefore when the foreground is rotated some of it gets covered by the background which now is in the front compared to the covered parts.
So my question is how can I keep the background completely in the back (i.e. not covering foreground regardless of the rotation)?
Below is my current implementation, the 2d background is generated in an offscreen canvas then put onto the main canvas with image() where the 3d stuff is generated, but I'll take any other approaches.
let bg;
p.setup = () => {
p.createCanvas(width,height,p.WEBGL);
bg = p.createGraphics(width,height);
}
p.draw = () => {
... // draw background bg
p.image(bg,x,y); // draw background on canvas
... // draw foreground
p.rotateX(degrees);//rotate
}
The best way to accomplish this is by clearing the WebGL depth buffer. This is buffer stores the depth for every pixel that has been draw so far so that as subsequent triangles are drawn they can be clipped if some or all of them is behind whatever was previously drawn at that location. This buffer is automatically cleared in between calls to draw() in p5.js but you can also call it yourself mid-frame:
let bg;
let zSlider;
let glContext;
function setup() {
let c = createCanvas(200, 200, WEBGL);
glContext = c.GL;
bg = createGraphics(width, height);
bg.background('red');
for (let y = 0; y < height; y += 20) {
for (let x = 0; x < width; x += 20) {
if ((x / 20 + y / 20) % 2 === 0) {
bg.fill('black');
} else {
bg.fill(
map(x + y, 0, width + height, 0, 360),
map(y, 0, height, 50, 100),
map(x, 0, width, 50, 100)
);
}
bg.square(x, y, 20)
}
}
zSlider = createSlider(0, width * 2, width);
zSlider.position(10, 10);
}
function draw() {
image(bg, -width / 2, -height / 2, width, height);
// Clear the z-buffer, subsequent drawing commands will not clip, even if they
// intersect with or are behind previously drawn elements (like our background
// image)
glContext.clear(glContext.DEPTH_BUFFER_BIT);
push();
translate(0, 0, (zSlider.value() - width) * 2);
rotateX(millis() / 1000 * PI / 4);
rotateY(millis() / 1000 * PI / 8);
box(100);
pop();
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.3.1/lib/p5.js"></script>
There is also kludgy solution that doesn't rely on calling WebGL internals, but I have only been able to make it work for square canvases:
Switch to an orthographic camera mode before drawing your background image.
Translate in the negative Z direction as far as possible without going beyond the "far" clipping plane.
Draw your background image.
Pop the state back to the normal perspective camera.
This uses orthographic projection to allow you to draw the background image behind the rest of the scene without diminishing size due to perspective. However I haven't come up with a fool proof way to determine what the perfect translation value is, nor how to reliably setup the orthographic project to control where the "far" clipping plane is.
let bg;
let zSlider;
function setup() {
createCanvas(200, 200, WEBGL);
bg = createGraphics(width, height);
bg.background('red');
for (let y = 0; y < height; y += 20) {
for (let x = 0; x < width; x += 20) {
if ((x / 20 + y / 20) % 2 === 0) {
bg.fill('black');
} else {
bg.fill(
map(x + y, 0, width + height, 0, 360),
map(y, 0, height, 50, 100),
map(x, 0, width, 50, 100)
);
}
bg.square(x, y, 20)
}
}
zSlider = createSlider(0, width * 2, width);
zSlider.position(10, 10);
}
function draw() {
push();
ortho();
translate(0, 0, min(width, height) * -0.13);
image(bg, -width / 2, -height / 2, width, height);
pop();
push();
translate(0, 0, (zSlider.value() - width) * 2);
rotateX(millis() / 1000 * PI / 4);
rotateY(millis() / 1000 * PI / 8);
box(100);
pop();
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.3.1/lib/p5.js"></script>
I was wondering what the best method was for changing the position of a shape within the canvas.
Here's what I have:
'use strict';
(function() {
const canvas = document.getElementsByClassName('canvas')[0],
c = canvas.getContext('2d');
//
c.beginPath();
c.moveTo(0, 50);
c.quadraticCurveTo(0, 0, 50, 0);
c.quadraticCurveTo(100, 0, 100, 50);
c.quadraticCurveTo(100, 100, 50, 100);
c.quadraticCurveTo(0, 100, 0, 50);
c.stroke();
})();
<canvas class="canvas" width="500" height="500"></canvas>
So right now that shape is sitting at 0,0. If I wanted to move it in the X direction I could theoretically do something like this:
c.beginPath();
c.moveTo(x+0,50);
c.quadraticCurveTo(x+0,0,x+50,0);
c.quadraticCurveTo(x+100,0,x+100,50);
c.quadraticCurveTo(x+100,100,x+50,100);
c.quadraticCurveTo(x+0,100,x+0,50);
c.stroke();
What's the correct way of achieving this?
As you ask "I was wondering what the best method was for changing the position of a shape within the canvas."
The answer is
By using the transformation matrix
Using direct manipulation of the coordinates is very inefficient.
The 2D API provides a full set of methods for transforming (moving) anything that is rendered.
These methods are;
ctx.setTransform(axisX_X, axisX_Y, axisY_X, axisY_Y, originX,
originY)
ctx.transform(axisX_X, axisX_Y, axisY_X, axisY_Y, originX, originY)
ctx.scale(scaleX, scaleY)
ctx.rotate(radians)
ctx.translate(originX, originY)
(links to MDN for each)
These will move anything that you render to the canvas apart from ctx.putPixelData
thus to move your object by x and y you can use
c.translate(x,y); // move the following coordinates by x, and y;
c.beginPath();
c.moveTo(0, 50);
c.quadraticCurveTo(0, 0, 50, 0);
c.quadraticCurveTo(100, 0, 100, 50);
c.quadraticCurveTo(100, 100, 50, 100);
c.quadraticCurveTo(0, 100, 0, 50);
c.stroke();
Or directly set the transform with
c.setTransform(1, 0, 0, 1, x, y);
c.beginPath();
c.moveTo(0, 50);
c.quadraticCurveTo(0, 0, 50, 0);
c.quadraticCurveTo(100, 0, 100, 50);
c.quadraticCurveTo(100, 100, 50, 100);
c.quadraticCurveTo(0, 100, 0, 50);
c.stroke();
These can be tricky to understand if you are not familiar with matrix manipulation as methods 2,3,4,5 from the above list are multiplications of the existing matrix. While method 1 simply replaces the existing matrix and is not effected by previous transformations.
Here is a very simple function that will move an object and includes scale and rotation.
// rotation is in radians and rotates the x and y axis
// scaleX scales along the new xAxis
// scaleY scales along the new yAxis
// x and y translate to the desired screen coordinates
function position(ctx, x, y, scaleX, scaleY, rotation){
var scaleRatio = scaleY / scaleX;
var rx = Math.cos(rotation) * scaleX;
var ry = Math.sin(rotation) * scaleX;
ctx.setTransform(rx, ry, -ry * scaleRatio, rx * scaleRatio, x, y);
}
to call
position(ctx,100,100,2,0.5, Math.PI/4)
moves anything drawn after that call 100,100 pixels, makes it twice as wide and half as high and rotates it 45 deg clockwise
to restore to the default do either
position(ctx, 0, 0, 1, 1, 0);
or quicker with
ctx.setTransform(1, 0, 0, 1, 0, 0);
For a more detailed explanation
of how to use setTransform and why it is prefered over the other methods see this answer
You've got the right idea. Just make sure that you're clearing the canvas between draws to make sure you're not drawing over what's currently on the canvas. Then you can just have some x value that you update and you can draw it anywhere. You could extend this to using some y value to allow you to move it vertically as well.
'use strict';
(function() {
const canvas = document.getElementsByClassName('canvas')[0],
c = canvas.getContext('2d');
var x = 0;
function render() {
c.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
c.beginPath();
// Adjust path based on x position
c.moveTo(x, 50);
c.quadraticCurveTo(x, 0, x + 50, 0);
c.quadraticCurveTo(x + 100, 0, x + 100, 50);
c.quadraticCurveTo(x + 100, 100, x + 50, 100);
c.quadraticCurveTo(x, 100, x, 50);
c.stroke();
// Doing this for animation
x += 5;
if (x > canvas.width) {
x = -100;
}
requestAnimationFrame(render);
}
render();
})();
<canvas class="canvas" width="500" height="500"></canvas>
Im study html canvas and trying to make simple animation.
I want to make rectangle move right.
You can look at my code here.
http://codepen.io/inkluter/pen/GgeQqj
var x = 0, y = 0, w = 200, h = 100;
function draw() {
c.clearRect(0, 0, c.width, c.height);
x++;
c.beginPath();
c.strokeRect(x, y, w, h);
c.closePath();
requestAnimationFrame(draw);
};
Problem is that clearRect() function seems not working. Old rectangle still displaying on canvas.
What am i doing wrong?
'width' and 'height properties are defined in the canvas element, not the context. Write c.clearRect(0, 0, canvas.width, canvas.height) instead.
I'm trying to learn how to draw/fill different shapes by using canvas and JavaScript, but my shapes doesn't get filled in the way I want them to, at all. The body of my HTML-document is this simple line:
<canvas id="canvas1" width="500" height="500"></canvas>
And my JavaScript-file looks like this:
function draw() {
var canvas1 = document.getElementById('canvas1');
if(canvas1.getContext) {
var ctx = canvas1.getContext('2d');
var gradient = ctx.createLinearGradient(0, 0, 50, 0);
gradient.addColorStop(0, "blue");
gradient.addColorStop(1, "white");
ctx.beginPath();
ctx.moveTo(25,25);
ctx.lineTo(100, 25);
ctx.stroke();
ctx.moveTo(25, 50);
ctx.bezierCurveTo(25, 50, 50, 80, 75, 60)
ctx.fillStyle = "black";
ctx.fill();
ctx.beginPath();
ctx.moveTo(75, 100);
ctx.arc(50, 100, 25, 0, Math.PI*2, true);
ctx.fillStyle = "black";
ctx.fill();
ctx.beginPath();
ctx.fillStyle = gradient;
ctx.arc(75, 150, 25, 0, Math.PI*2, true);
ctx.fill();
}
}
But this is the result:
And I don't get it. I've tried filling my second circle with every other color, and that works just fine. Also if I remove the last "ctx.beginPath();" my first circle gets painted in gradient. But I can't get the same bug to work on my second circle by changing the position of the code or something. And every guide I've found tells me that this should work, as far as I understand it.
Gradients are defined with an absolute position so if you draw your circle outside the area defined by the gradient it will appear transparent instead of filled.
There is no need to close the path as the fill() method will close it implicit for you, but just make sure the coordinates in the gradient covers the area you want to fill.
Instead of calculating for each time you need to fill an arc you could create a generic wrapper function which takes a position and colors to fill (adjust as needed):
A demo here
/**
* Fills a circle with a two-color gradient.
* #param {Number} cx - center X
* #param {Number} cy - center Y
* #param {Number} radius - radius
* #param {String} col1 - start color as CSS color string
* #param {String} col2 - end color as CSS color string
* #param {Boolean} [horiz=false] - Set true for horizontal gradient
*/
function fillCircle(cx, cy, radius, col1, col2, horiz) {
var x = cx - radius,
y = cy - radius,
d = radius * 2,
gradient;
if (horiz) {
gradient = ctx.createLinearGradient(x, 0, x+d, d);
}
else {
gradient = ctx.createLinearGradient(0, y, 0, y+d);
}
gradient.addColorStop(0, col1);
gradient.addColorStop(1, col2);
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2*Math.PI);
ctx.fill();
}
Then just use it this way:
fillCircle(200, 200, 70, 'yellow', 'red');
The last flag is optional here and makes a horizontal gradient if set to true.
Use ctx.closePath(); After each separate shape/line you want is done.
ctx.beginPath();
ctx.moveTo(25, 50);
ctx.bezierCurveTo(25, 50, 50, 80, 75, 60)
ctx.strokeStyle = "black";
ctx.stroke();
ctx.closePath();
The gradient needs to be set with the coordinates matching where your shape is on the canvas.
You have the gradient starting at 0,0,
var gradient = ctx.createLinearGradient(0, 0, 50, 0);
But your circle is locates at 25,50. Make your gradient coordinates the same as you circle coordinates.
http://jsfiddle.net/bC75t/1/
It appears the only way to clear a region from a canvas is to use the clearRect() command - I need to clear a circle (I am masking out areas from a filled canvas, point lights in this specific case) and despite all attempts it does not seem possible.
I tried drawing a circle with an alpha value of 0 but simply nothing would appear unless the alpha was higher (which is counter to the point :P) - I assume because a contex.fill() draws it as an add rather than a replace.
Any suggestions on how I might be able to (quickly) clear circles for mask purposes?
Use .arc to create a circular stroke and then use .clip() to make that the current clipping region.
Then you can use .clearRect() to erase the whole canvas, but only the clipped area will change.
If you're making a game or something where squeezing every bit of performance matters, have a look at how I made this answer: Canvas - Fill a rectangle in all areas that are fully transparent
Specifically, the edit of the answer that leads to this: http://jsfiddle.net/a2Age/2/
The huge plusses here:
No use of paths (slow)
No use of clips (slow)
No need for save/restore (since there's no way to reset a clipping region without clearing all state(1), it means you must use save/restore also)
(1) I actually complained about this and resetClip() has been put in the offical spec because of it, but it will be a while before browsers implement it.
Code
var ctx = document.getElementById('canvas1').getContext('2d'),
ambientLight = 0.1,
intensity = 1,
radius = 100,
amb = 'rgba(0,0,0,' + (1 - ambientLight) + ')';
addLight(ctx, intensity, amb, 200, 200, 0, 200, 200, radius); // First circle
addLight(ctx, intensity, amb, 250, 270, 0, 250, 270, radius); // Second circle
addLight(ctx, intensity, amb, 50, 370, 0, 50, 370, radius, 50); // Third!
ctx.fillStyle = amb;
ctx.globalCompositeOperation = 'xor';
ctx.fillRect(0, 0, 500, 500);
function addLight(ctx, intsy, amb, xStart, yStart, rStart, xEnd, yEnd, rEnd, xOff, yOff) {
xOff = xOff || 0;
yOff = yOff || 0;
var g = ctx.createRadialGradient(xStart, yStart, rStart, xEnd, yEnd, rEnd);
g.addColorStop(1, 'rgba(0,0,0,' + (1 - intsy) + ')');
g.addColorStop(0, amb);
ctx.fillStyle = g;
ctx.fillRect(xStart - rEnd + xOff, yStart - rEnd + yOff, xEnd + rEnd, yEnd + rEnd);
}
canvas {
border: 1px solid black;
background-image: url('http://placekitten.com/500/500');
}
<canvas id="canvas1" width="500" height="500"></canvas>
Given the requirements, these answers are fine. But lets say you're like me and you have additional requirements:
You want to "clear" a part of a shape that may be partially outside the bounds of the shape you're clearing.
You want to see the background underneath the shape instead of clearing the background.
For the first requirement, the solution is to use context.globalCompositeOperation = 'destination-out' The blue is the first shape and the red is the second shape. As you can see, destination-out removes the section from the first shape.
Here's some example code:
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
Here's the potential problem with this: The second fill() will clear everything underneath it, including the background. Sometimes you'll want to only clear the first shape but you still want to see the layers that are underneath it.
The solution to that is to draw this on a temporary canvas and then drawImage to draw the temporary canvas onto your main canvas. The code will look like this:
diameter = projectile.radius * 2
console.log "<canvas width='" + diameter + "' height='" + diameter + "'></canvas>"
explosionCanvas = $("<canvas width='" + diameter + "' height='" + diameter + "'></canvas>")
explosionCanvasCtx = explosionCanvas[0].getContext("2d")
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
durationPercent = (projectile.startDuration - projectile.duration) / projectile.startDuration
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'source-over' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
ctx.drawImage(explosionCanvas[0], projectile.pos.x - projectile.radius, projectile.pos.y - projectile.radius) #center
You have a few options.
Firstly, here's a function we'll use to fill a circle.
var fillCircle = function(x, y, radius)
{
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
};
clip()
var clearCircle = function(x, y, radius)
{
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.clip();
context.clearRect(x - radius - 1, y - radius - 1,
radius * 2 + 2, radius * 2 + 2);
};
See this on jsFiddle.
globalCompositeOperation
var clearCircle = function(x, y, radius)
{
context.save();
context.globalCompositeOperation = 'destination-out';
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
context.restore();
};
See this on jsFiddle.
Both gave the desired result on screen, however the performance wasn't sufficient in my case as I was drawing and clearing a lot of circles each frame for an effect. In the end I found a different way to get a similar effect to what I wanted by just drawing thicker lines on an arc, but the above may still be useful to someone having different performance requirements.
Use canvas.getContext("2d").arc(...) to draw a circle over the area with the background colour?
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.arc(x, y, r, 0, 2*Math.PI, false);
context.fillStyle = "#FFFFFF";
context.fill();
Where x = left position, y = right position, r = radius, and ctx = your canvas:
function clearCircle( x , y , r ){
for( var i = 0 ; i < Math.round( Math.PI * r ) ; i++ ){
var angle = ( i / Math.round( Math.PI * r )) * 360;
ctx.clearRect( x , y , Math.sin( angle * ( Math.PI / 180 )) * r , Math.cos( angle * ( Math.PI / 180 )) * r );
}
}