I am trying to form a cell object using paths in HTML5 Canvas. But I am unable to apply fill for the path I made. Tried out several things but not able to solve it.
class Cell {
constructor({ i, j }) {
this.i = i;
this.j = j;
this.visited = false;
this.walls = [true, true, true, true]; // top right bottom left
this.show = canvasContext => this._show(canvasContext);
}
_show(ctx) {
const x = this.j * cellSize;
const y = this.i * cellSize;
if (!this.visited) {
ctx.fillStyle = "green";
} else {
ctx.fillStyle = "yellow";
}
// ctx.fillStyle = "green";
ctx.strokeStyle = "black";
ctx.beginPath();
if (this.walls[0]) {
ctx.moveTo(x, y);
ctx.lineTo(x + cellSize, y); // top
}
if (this.walls[3]) {
ctx.moveTo(x, y);
ctx.lineTo(x, y + cellSize); // left
}
if (this.walls[1]) {
ctx.moveTo(x + cellSize, y);
ctx.lineTo(x + cellSize, y + cellSize); // right
}
if (this.walls[2]) {
ctx.moveTo(x, y + cellSize);
ctx.lineTo(x + cellSize, y + cellSize); // bottom
}
ctx.fill();
ctx.stroke();
// ctx.fill();
}
}
See the Pen Maze Generator by Dhanushu (#dhanushuUzumaki) on CodePen.
The problem appears to be that you aren't creating a closed shape, you are just creating a series of lines that don't form a shape.
This is a closed path. If you took a pencil and drew the points, you'd see that it would be closed.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#F00"; // red
ctx.strokeStyle = "#0F0"; // green
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(100, 50);
ctx.lineTo(100, 100);
ctx.lineTo(50, 100);
ctx.closePath();
ctx.fill();
ctx.stroke();
canvas {
border: 1px solid #000;
}
<canvas />
This is an open path, who's stroke looks the same:
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#F00"; // red
ctx.strokeStyle = "#0F0"; // green
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(100, 50);
ctx.moveTo(100, 50);
ctx.lineTo(100, 100);
ctx.moveTo(100, 100);
ctx.lineTo(50, 100);
ctx.moveTo(50, 100);
ctx.lineTo(50, 50);
ctx.closePath();
ctx.fill();
ctx.stroke();
canvas {
border: 1px solid #000;
}
<canvas />
By calling moveTo() you are essentially picking the pencil up, and breaking the shape. There is nothing to fill, because it's not one contiguous path. It's just 4 separate lines that are close to one another.
For your particular problem, I see two ways to solve this. The first would be to stop using moveTo() so often and just draw the lines. However, for your particular problem, that could be tricky.
Instead, what you might draw to do is draw individual rectangles to fill in each block of your grid. It seems like that logic might be easier to work out.
Related
This is my draw function for the canvas:
const drawSector = (sector, i) => {
const ang = arc * i;
ctx.save();
// COLOR
ctx.lineWidth = 10;
ctx.strokeStyle = "#ECBA3F";
ctx.beginPath();
ctx.fillStyle = sector.color;
ctx.moveTo(rad, rad);
ctx.arc(rad, rad, rad, ang, ang + arc);
ctx.lineTo(rad, rad);
ctx.fill();
ctx.stroke();
// TEXT
ctx.translate(rad, rad);
ctx.rotate(ang + arc / 2);
ctx.textAlign = "right";
ctx.fillStyle = "#000";
ctx.font = "bold 10px sans-serif";
ctx.fillText(sector.label, rad - 10, 10);
//
ctx.restore();
};
Here's my sandbox
As soon as I set a linewidth on the canvas, part of the edges get cutoff seemingly as if the canvas width and height don't take the linewidth into account. How do I fix this?
Also is there a way I can rotate the text inside the arcs so that they stack rather than just being in a single line. Perhaps something like the below image?
I have a project to do and I cannot do it until I understand how moving object inside canvas work.
I need to move one of the objects below by 50pixels to right.
Anyone willing to help me is greatly appreciated.
Meanwhile thank you very much in advance for your help or suggestions.
function canvasOneShape() {
//refers to the html canvasone id
var canvas = document.getElementById("canvasOne");
this.canvasOne.width = 945;
this.canvasOne.height = 650;
// draws the canvas in 2d
var ctx = canvas.getContext("2d");
// Set the fill colour to blue.
ctx.fillStyle = "blue"; //used like this instead of rgb due personal preference:)
// Create a filled rectangle at co-ordinates (10,10)
// with height and width set to 100.
ctx.fillRect(10, 10, 250, 330); //
// Here I draw the square
// Set the canvas up for drawing in 2D.
// Set the fill colour to blue.
ctx.fillStyle = "rgba(244, 244, 189,.5)";
ctx.fillRect(10, 50, 330, 250);
//draw my first circle
var midXone = canvas.width / 2; //x location
var midXtwo = canvas.height / 2; //y location
var radius = 60; //circle radius
ctx.beginPath();
ctx.arc(midXone, midXtwo, radius, 0, 2 * Math.PI, false);
ctx.fillStyle ="rgba(89, 192, 227,.4)";
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = '#003300';
ctx.stroke();
//draw the second circle
var midX = canvas.width / 2.35; //x location
var midY = canvas.height / 2.35; //y location
var radius = 50; //circle radius
ctx.beginPath();
ctx.arc(midX, midY, radius, 0, 2 * Math.PI, false);
ctx.fillStyle ="rgba(66, 244, 89,.4)";
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle ="rgba(255, 244, 9,.4)";
ctx.stroke();
//draw Square with circle inside
//square
ctx.fillStyle = "rgb(222, 33, 51)";
ctx.fillRect(550, 20, 300, 300);
//circle
ctx.beginPath();
ctx.arc(700, 170, 150, 0, 2 * Math.PI, false);
ctx.fillStyle ="rgba(66, 244, 89,.4)";
ctx.fill();
ctx.lineWidth = 1;
ctx.strokeStyle = '#f44242';
ctx.stroke();
//The Pacman object
var radius = 100; //circle radius
var x = 100;
var y = 500;
ctx.beginPath();
ctx.arc(120, 500, radius, 1.85 * Math.PI, .15 * Math.PI, true);
//Draw mouth
ctx.lineTo(120, 500);
ctx.closePath();
ctx.fillStyle = "rgb(255, 255, 0)";
ctx.fill();
ctx.lineWidth = 5;
ctx.strokeStyle = 'rgb(0,0,0)';
ctx.stroke();
//draw eye
ctx.beginPath();
ctx.arc(x + 40, y - 40, 10, 0 * Math.PI, 2 * Math.PI, true);
ctx.fillStyle = "rgb(0,0,0)";
ctx.fill();
}
there are no layers or objects on a canvas(it's just a single layer of pixels), so, if you draw something, you overwrite what's underneath it. to move something to the right, you will need to have a function to draw everything behind it, and then variables to store the location (x and y) of the objects you want to move.
then, to move it, you clear the canvas with
ctx.clearRect(0, 0,width of canvas, height height of canvas);,
call the background function to draw everything behind it again, and the redraw your object in a different location.
I can't see that this had been posted already, so here goes.
Let's say i draw 2 squares on the canvas.
var c = document.getElementById('test'), ctx = c.getContext('2d');
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.fill();
This produces this image:
If i change globalAlpha to 0.5, i get this:
However, i want to produce this:
As in, all pixels are transparent and any images under it will appear, but the pixels created by the red triangle will overwrite the existing blue triangle where it is drawn.
And ctx.globalComposisteOperation doesn't seem to help in this instance due to it also factoring the transparency and the fact i want to keep both squares.
Is there any way to do this with current methods?
Use Compositing to clear the red triangle before drawing it.
Using compositing is slightly better than clipping because you don't have to clear the clip. Clearing a clip requires saving the entire context state and then restoring that context state -- many properties involved. Compositing just requires changing 1 property forth and back.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// fill the blue rect
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
// define the red rect path
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
// clear the red rect path using compositing
ctx.globalCompositeOperation='destination-out';
ctx.fillStyle='black';
ctx.fill();
ctx.globalCompositeOperation='source-over';
// fill the red rect
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.fill();
body{ background-color:white; }
#canvas{border:1px solid red; }
<canvas id="canvas" width=512 height=512></canvas>
Layers
Do it the photoshop way and create layers to do the work for you. Creating a layer (second canvas) is no more trouble than loading an image. You can create dozens and have no problem and it makes this type of work easy.
First create a second canvas (layer)
// canvas is original canvas
var layer = document.createElement("canvas");
layer.width = canvas.width; // same size as original
layer.height = canvas.height;
var ctx1 = layer.getContext("2d");
Then draw your triangles on the second canvas with alpha = 1;
var tri = (x,c)=>{
ctx1.fillStyle = c;
ctx1.beginPath();
ctx1.moveTo(25 + x, 0);
ctx1.lineTo(50 + x, 50);
ctx1.lineTo(0 + x, 50);
ctx1.closePath();
ctx1.fill();
}
tri(0,"#00f");
tri(25,"#f00");
Then just draw that layer on top of the canvas you are working on with the alpha value you want.
ctx.globalAlpha = 0.5;
ctx.drawImage(layer,0,0);
If you don't need the extra layer delete the canvas and context by dereferencing them.
ctx1 = undefined;
layer = undefined;
Or you can keep the layer , and make another layer for the background and mix them in real time to get the FX just right
//
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
var layer = document.createElement("canvas");
layer.width = canvas.width; // same size as original
layer.height = canvas.height;
var ctx1 = layer.getContext("2d");
var tri = (x,c)=>{
ctx1.fillStyle = c;
ctx1.beginPath();
ctx1.moveTo(25 + x, 0);
ctx1.lineTo(50 + x, 50);
ctx1.lineTo(0 + x, 50);
ctx1.fill();
}
tri(0,"#00f");
tri(25,"#0f0");
tri(50,"#f00");
ctx.globalAlpha = 0.5;
ctx.drawImage(layer,0,0);
layer = ctx1 = undefined;
You can clear the area for the second rectangle before drawing it.
var c = document.getElementById('game'),
ctx = c.getContext('2d');
ctx.globalAlpha = 0.5;
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 50, 50);
// Clear the extra bit of the blue rectangle
ctx.clearRect(25, 25, 25, 25);
ctx.fillStyle = "red";
ctx.fillRect(25, 25, 50, 50);
<canvas id="game" width="320" height="240"></canvas>
I would honestly suggest just changing the colors and avoid using an alpha.
var c = document.getElementById('game'),
ctx = c.getContext('2d');
ctx.fillStyle = "#8080FF";
ctx.fillRect(0, 0, 50, 50);
ctx.fillStyle = "#ff8080";
ctx.fillRect(25, 25, 50, 50);
<canvas id="game" width="320" height="240"></canvas>
var c = document.getElementById('test'), ctx = c.getContext('2d');
ctx.save();
ctx.fillStyle = "rgba(0,0,255,0.5)";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.clip();
ctx.clearRect(0, 0, 100, 100);
ctx.fill();
ctx.restore();
Just use clip() to take the current path, clear everything in there with clearRect, then draw the path as normal.
#markE is right, use compositing instead of clipping (really heavy).
However, his solution will only work if you've drawn everything with an rgba color. Maybe I read your question wrongly, but if you're going from an opaque shape and want to make it transparent, then you should rather use the copy gCO and the globalAlpha property.
This will have less performance impact since drawImage is faster than fill, and will allow you to perform a fade-out effect ; but it really depends on your needs.
var ctx = c.getContext('2d');
// initial blue
ctx.fillStyle = "#0000FF";
ctx.beginPath();
ctx.moveTo(25, 0);
ctx.lineTo(50, 50);
ctx.lineTo(0, 50);
ctx.lineTo(25, 0);
ctx.fill();
setTimeout(function drawRed() {
ctx.fillStyle = "#FF0000";
ctx.beginPath();
ctx.moveTo(50, 0);
ctx.lineTo(75, 50);
ctx.lineTo(25, 50);
ctx.lineTo(50, 0);
ctx.fill();
}, 500);
btn.onclick = function makeItTransparent() {
// if we weere to make this into an animation, we would set it only once
ctx.globalCompositeOperation = 'copy';
ctx.globalAlpha = .8;
// draw the canvas on itself
ctx.drawImage(c, 0, 0);
// once again, in an animation we won't reset this to default
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
};
/* checkboard background */
canvas {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGElEQVQYlWNgYGBowIKxgqGgcJA5h3yFAOI3GQFqqi5ZAAAAAElFTkSuQmCC");
}
<canvas id="c"></canvas>
<button id="btn">make it transparent</button>
Seeking a canvas and try once in the case.
I draw such a thing jsfiddle.
var widthCanvas = 960;
var heightCanvas = 1910;
var widthWhiteLine = 250;
var startY = 0;
//shadow
ctx.shadowColor = '#111';
ctx.shadowBlur = 20;
//right top stroke
ctx.beginPath();
ctx.lineWidth = widthWhiteLine;
ctx.shadowColor = '#111';
ctx.shadowBlur = 20;
ctx.strokeStyle = '#f2f2f2';
ctx.moveTo( 1920 + widthWhiteLine /1.5 , 0);
ctx.lineTo(widthCanvas - widthWhiteLine /1.6, widthCanvas + widthWhiteLine *1.4);
ctx.stroke();
//left top stroke
ctx.beginPath();
ctx.lineWidth = widthWhiteLine;
ctx.strokeStyle = '#f2f2f2';
ctx.moveTo(-widthWhiteLine /1.4 , startY);
ctx.lineTo(widthCanvas + widthWhiteLine / 1.6, widthCanvas + widthWhiteLine *1.4);
ctx.stroke();
//left bottom stroke
ctx.beginPath();
ctx.lineWidth = widthWhiteLine;
ctx.strokeStyle = '#f2f2f2';
ctx.moveTo(-widthWhiteLine /1.4, heightCanvas);
ctx.lineTo(widthCanvas + widthWhiteLine / 1.6, widthCanvas - widthWhiteLine *1.4);
ctx.stroke();
ctx.closePath;
//right bottom stroke
ctx.beginPath();
ctx.lineWidth = widthWhiteLine;
ctx.strokeStyle = '#f2f2f2';
ctx.moveTo(1920 + widthWhiteLine /1.4, heightCanvas);
ctx.lineTo(widthCanvas - widthWhiteLine /1.6 , widthCanvas - widthWhiteLine *1.4);
ctx.stroke();
ctx.closePath;
After drawing for the correct display of the cross-hairs in the middle, I need to have an element of that right top stroke has become higher than the right bottom stroke but not higher than the left top stroke.
This can be done using the canvas?
You'll need to manage the layering on your own. The best way to do that is to put your draw routines into functions then run the functions in order of bottom to top. I've updated you're fiddle to give you an idea of what I'm talking about.
// pseudo code
function drawSquare() {
...
}
function drawCircle() {
...
}
drawCircle();
drawSquare();
I made a cylinder gauge, very similar to this one:
It is drawn using about 7 or so functions... mine is a little different. It is very fleixble in that I can set the colors, transparency, height, width, whether there is % text shown and a host of other options. But now I have a need for the same thing, but all rotated 90 deg so that I can set the height long and the width low to generate something more like this:
I found ctx.rotate, but no mater where it goes all the shapes fall apart.. ctx.save/restore appears to do nothing, I tried putting that in each shape drawing function. I tried modifying, for example, the drawOval function so that it would first rotate the canvas if horizontal was set to one; but it appeared to rotate it every single iteration, even with save/restore... so the top cylinder would rotate and the bottom would rotate twice or something. Very tough to tell what is really happening. What am I doing wrong? I don't want to duplicate all this code and spend hours customizing it, just to produce something I already have but turned horizontal. Erg! Help.
Option 1
To rotate everything just apply a transform to the element itself:
canvas.style.transform = "rotate(90deg)"; // or -90 depending on need
canvas.style.webkitTransform = "rotate(90deg)";
Option 2
Rotate context before drawing anything and before using any save(). Unlike the CSS version you will first need to translate to center, then rotate, and finally translate back.
You will need to make sure width and height of canvas is swapped before this is performed.
ctx.translate(ctx.canvas.width * 0.5, ctx.canvas.height * 0.5); // center
ctx.rotate(Math.PI * 0.5); // 90°
ctx.translate(-ctx.canvas.width * 0.5, -ctx.canvas.height * 0.5);
And of course, as an option 3, you can recalculate all your values to go along the other axis.
Look at the rotate function in this example. You want to do a translation to the point you want to rotate around.
example1();
example2();
function rotate(ctx, degrees, x, y, fn) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(degrees * (Math.PI / 180));
fn();
ctx.restore();
}
function rad(deg) {
return deg * (Math.PI / 180);
}
function example2() {
var can = document.getElementById("can2");
var ctx = can.getContext('2d');
var w = can.width;
var h = can.height;
function drawBattery() {
var percent = 60;
ctx.beginPath();
ctx.arc(35,50, 25,0,rad(360));
ctx.moveTo(35+percent+25,50);
ctx.arc(35+percent,50,25,0,rad(360));
ctx.stroke();
ctx.beginPath();
ctx.fillStyle = "rgba(0,255,0,.5)";
ctx.arc(35,50,25,0,rad(360));
ctx.arc(35+percent,50,25,0,rad(360));
ctx.rect(35,25,percent,50);
ctx.fill();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "#666666";
ctx.moveTo(135,25);
ctx.arc(135,50,25, rad(270), rad(269.9999));
//ctx.moveTo(35,75);
ctx.arc(35,50,25,rad(270),rad(90), true);
ctx.lineTo(135,75);
ctx.stroke();
}
drawBattery();
can = document.getElementById("can3");
ctx = can.getContext('2d');
w = can.width;
h = can.height;
rotate(ctx, -90, 0, h, drawBattery);
}
function example1() {
var can = document.getElementById('can');
var ctx = can.getContext('2d');
var color1 = "#FFFFFF";
var color2 = "#FFFF00";
var color3 = "rgba(0,155,255,.5)"
var text = 0;
function fillBox() {
ctx.save();
ctx.fillStyle = color3;
ctx.fillRect(0, 0, can.width / 2, can.height);
ctx.restore();
}
function drawBox() {
ctx.save();
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = color1;
ctx.rect(10, 10, 50, 180);
ctx.font = "30px Arial";
ctx.fillText(text, 25, 45);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = color2;
ctx.lineWidth = 10;
ctx.moveTo(10, 10);
ctx.lineTo(60, 10);
ctx.stroke();
ctx.restore();
}
fillBox();
rotate(ctx, 90, can.width, 0, fillBox);
text = "A";
drawBox();
color1 = "#00FFFF";
color2 = "#FF00FF";
text = "B";
rotate(ctx, 90, can.width, 0, drawBox);
centerRotatedBox()
function centerRotatedBox() {
ctx.translate(can.width / 2, can.height / 2);
for (var i = 0; i <= 90; i += 10) {
var radians = i * (Math.PI / 180);
ctx.save();
ctx.rotate(radians);
ctx.beginPath();
ctx.strokeStyle = "#333333";
ctx.rect(0, 0, 50, 50)
ctx.stroke();
ctx.restore();
}
}
}
#can,
#can2,
#can3 {
border: 1px solid #333333
}
<canvas id="can" width="200" height="200"></canvas>
<canvas id="can2" width="200" height="100"></canvas>
<canvas id="can3" width="100" height="200"></canvas>