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/
Related
I have an issue where I am grabbing a users displayAvatar() and then I use an arc to make the image round. This works fine, however, I need to place a circle on top of that image, but it is getting cut in half because of the previous clip()
Without clip() : https://i.gyazo.com/b474c656f33a1f004f5e3becffcef527.png
With clip() : https://i.gyazo.com/da13377cd3f6dc7516c2b8fd1f0f8ac9.png
I know that in the 'With clip()' image, it appears as if the arc border is showing outside of the clip, but that is hardcoded into the image, I put it as a guide with an image editor.
I tried moving around the code, I removed the line ctx.clip() and saw that my circle displays fine on top of the image.
// circle around avatar
ctx.beginPath();
ctx.arc(122.5, 141.8, 81, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
const avatar = await Canvas.loadImage(
message.author.displayAvatarURL({ format: 'png' })
);
ctx.strokeStyle = '#ffffff';
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(avatar, 41.5, 60.5, 162, 162);
// presence circle
ctx.beginPath();
ctx.arc(184.5, 193.5, 19, 0, Math.PI * 2, true);
ctx.strokeStyle = '#000000';
ctx.lineWidth = 8;
ctx.stroke();
ctx.fillStyle = userStatusColor;
ctx.fill();
Take a look at the canvas clip() definition:
https://www.w3schools.com/tags/canvas_clip.asp
Tip: Once a region is clipped, all future drawing will be limited to the clipped region (no access to other regions on the canvas). You can however save the current canvas region using the save() method before using the clip() method, and restore it (with the restore() method) any time in the future.
Below is an example using the save and restore
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(90, 90, 81, 0, Math.PI * 2, true);
ctx.stroke();
ctx.save();
ctx.clip();
ctx.beginPath();
ctx.arc(150, 50, 19, 0, Math.PI * 2, true);
ctx.fillStyle = '#0000ff';
ctx.lineWidth = 8;
ctx.stroke();
ctx.fill();
ctx.restore();
ctx.beginPath();
ctx.arc(170, 99, 19, 0, Math.PI * 2, true);
ctx.fillStyle = '#ff0000';
ctx.lineWidth = 8;
ctx.stroke();
ctx.fill();
<canvas id="canvas"></canvas>
Inspiring myself from this answer, I want to make several progress circle in my page (about 30). It works perfectly for one but I don't get the circle for the values. I however get the percentage displayed correctly.
I've tried various thing, adding [count] to most of the options but still the circle is not drawn for each cell.
I added my code in this Fiddle.
Can you see what's wrong?
Your drawCircle function needs a bit more information (ctx and radius)
var drawCircle = function(ctx, radius, color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth;
ctx.stroke();
};
and you need to pass it that info when using it:
drawCircle(ctx[count], radius[count], '#efefef', options[count].lineWidth, 100 / 100);
drawCircle(ctx[count], radius[count], color[count], options[count].lineWidth, options[count].percent / 100);
like here: https://fiddle.jshell.net/6ooL53pp/3/
I am trying to using context.clip() to clip a draw arc from other one and fill the clipped result.
But when i clip section & fill it, it gives pixelated fill .
var ctx = document.getElementById("canvas").getContext("2d");
var x = 150 ;
var y = 150 ;
var r = 100 ;
ctx.save() ;
ctx.translate(x,y) ;
ctx.beginPath() ;
ctx.arc(0,0,r,0,2*Math.PI);
ctx.closePath() ;
ctx.fillStyle = "cyan" ;
ctx.fill() ;
ctx.lineWidth = 10;
ctx.stroke();
ctx.restore() ;
ctx.save() ;
ctx.clip() ;
ctx.translate(x,y);
ctx.beginPath();
ctx.moveTo(r,-r-10);
ctx.arc(0,-r-10,r,0,Math.PI*2);
ctx.closePath();
ctx.fillStyle = "#f2f2f2";
ctx.fill();
ctx.lineWidth = 1;
ctx.stroke();
ctx.restore();
https://jsfiddle.net/x0d0n40z/1/
An alternative approach which eliminates the need for clip()/save()/restore() is to use a few steps of compositing.
Clipping mask is anti-aliased in some browsers while in other not. To obtain consistency (and in some cases also performance since save-clip-restore are relative expensive operations) using composition is preferred if possible.
In this case:
Fill main arc in target color
Define a clipping arc
Change composite mode to destination-out and fill (will cut main)
Change composite mode to source-atop and stroke (will outline cut)
Change composite mode to source-over and stroke outline of main circle
Example
Update: Simplified steps (with the last step merged into the process, ref. comments). I also chose to demonstrate use of the Path2D since we can reuse the object without interfering with the path on the ordinary context -
var ctx = c.getContext("2d"),
p = new Path2D(), // this will store main shape for reuse
x = 75, y = 75, radius = 70;
// main arc
p.arc(x, y, radius, 0, 6.28); // store to path object
ctx.fillStyle = "cyan";
ctx.fill(p); // fill path object
// clip top arc
ctx.globalCompositeOperation = "source-atop";
ctx.arc(x, y - radius, radius, 0, 6.28);
ctx.fillStyle = "#09f";
ctx.fill();
ctx.lineWidth = 5;
ctx.stroke();
// stroke main arc
ctx.globalCompositeOperation = "source-over";
ctx.stroke(p); // stroke path object
body {background:#e9e9e9}
<canvas id=c></canvas>
Old version:
var ctx = c.getContext("2d"),
x = 75, y = 75, radius = 70;
// main arc
ctx.arc(x, y, radius, 0, 6.28);
ctx.fillStyle = "cyan";
ctx.fill();
// clipping arc
ctx.beginPath();
ctx.arc(x, y - radius, radius, 0, 6.28);
// cut step
ctx.globalCompositeOperation = "destination-out";
ctx.fill();
// stroke gap step
ctx.globalCompositeOperation = "source-atop";
ctx.lineWidth = 10;
ctx.stroke();
// stroke whole outline
ctx.globalCompositeOperation = "source-over";
ctx.beginPath();
ctx.arc(x, y, radius, 0, 6.28);
ctx.lineWidth = 5;
ctx.stroke();
// if you want to color the clip then use this:
ctx.globalCompositeOperation = "destination-atop";
ctx.fillStyle = "#09f";
ctx.fill();
body {background:#e9e9e9}
<canvas id=c></canvas>
The problem is that the clip boundary is not being anti alised.
To solve you can render the shape without using the clip. The ctx.arc method lets you set the start and end angles so you can get the inset by filling two arcs.
You will need to get the angles where the clip circle and the inset circle intercept.
For this case it is very simple. First get the distance between the circles, and the angle from one to the other. This works only for two circles of same radius.
var c = {x:?,y:?}; // circle one location
var c1 = {x:?,y:?}; // circle two location
var radius = ?; // radius of both
var angle = Math.atan2(c1.y - c.y, c1.x - c.x); // get the angle from one to the next
var dist = Math.hypot(c1.x - c.x, c1.y - c.y); // get the distance. NOTE IE does not have hypot so do it the normal way with Math.sqrt....
Now you have the angle and distance the intercepts are a simple relationship between the distance and the radius
var iAngle = Math.acos(dist / 2 / radius); // the angle from the line between the circles
// to the intercepts
Now you have that angle you can draw the two arcs
ctx.beginPath();
ctx.arc(c.x,c.y,radius,angle - iAngle , angle + iAngle); // first arc
ctx.arc(c1.x,c1.y, radius, angle + Math.PI - iAngle, angle + Math.PI + iAngle); // second arc
ctx.fill();
ctx.stroke();
There is not much you can do to prevent the jaggies from effecting the clip area. Another way to achieve clipping is to use ctx.globalCompositeOperation to render a mask. You can mask in and out, and many more options. This will be a better solution when the clipping area becomes more complex.
I finally figured the right way to correct the bug .
Heres the clean result of what i wanted https://jsfiddle.net/x0d0n40z/6/
Code :
var ctx = document.getElementById("canvas").getContext("2d");
var r = 50
x = ctx.canvas.width/2;
y = ctx.canvas.height/2;
var offset = 60;
ctx.save();
ctx.setTransform(1,0,0,1.5,x,y);
ctx.beginPath();
ctx.arc(0,0,r,0,2*Math.PI);
ctx.stroke();
ctx.clip();
ctx.beginPath();
ctx.arc(0,0,r,0,2*Math.PI,false);
ctx.fillStyle = "cyan";
ctx.fill();
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.beginPath();
ctx.arc(0,-offset,r,0,2*Math.PI,false);
ctx.fillStyle = "#f2f2f2";
ctx.fill();
ctx.lineWidth = 1 ;
ctx.stroke();
ctx.setTransform(1,0,0,1.5,x,y);
ctx.beginPath();
ctx.arc(0,0,r,0,2*Math.PI,false);
ctx.lineWidth = 3 ;
ctx.stroke();
ctx.restore();
Source from were i learned to use clip : http://www.html5canvastutorials.com/advanced/html5-canvas-clipping-region-tutorial/
I would like to create a circle with a special shadow effect.
Like this one:
.
It should look like a cone in wood or metal.
I tried to do something with the radial gradiant in canvas but i can't creat that special Effekt.
I donĀ“t know how to create the this shadow effect.
Can somebody give me a tip or help me?
This is what I've tried: JSFiddle
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 70,
radius = 60;
ctx.arc(x, y, radius, 0, 2 * Math.PI);
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, '#FF9900');
gradient.addColorStop(1, '#FFFFFF');
ctx.fillStyle = gradient;
ctx.fill();
Greetings from Germany
Matzuman
There is unfortunately no gradient type in canvas which allow you to specify a radiant gradient. You have to provide a mechanism to do so manually.
You could use a shadow approach drawing the object off-screen while offsetting the shadow so it overlapped the cone base. One for light and one for dark side.
You can achieve a better effect though by drawing a "light/dark stripe" rotated around the center at varying opacity levels depending on angle.
Example "rendering" the cone
This example allow you to adjust parameters like how visible the reflections should be, what colors, size of cone etc. Experiment with the values to find what you're after.
To offset the "light source", just rotate one time initially with the angle you need before rendering the overlapping stripes.
var ctx = document.querySelector("canvas").getContext("2d"),
cx = 75, cy = 75, radius = 70, // for arc/cone
maxOpacity = 1, // max opacity (will accumulate)
angleStep = 0.01, // "resolution"
angle = 0, t; // current angle and opacity
// draw base of cone
ctx.fillStyle = "rgb(139, 108, 33)";
ctx.arc(cx, cy, radius, 0, 2*Math.PI);
ctx.fill();
// now rotate around center drawing a white stripe at varying opacity
// depending on angle
ctx.fillStyle = "rgb(181, 159, 109)";
ctx.translate(cx, cy); // pivot for rotation = center of cone
// half of the cone is done with white overlay
for(angle = 0; angle < Math.PI; angle += angleStep) {
// calculate t [0,1] based on angle. Multiply with max opacity
t = (angle < Math.PI * 0.5 ? angle : Math.PI - angle) / Math.PI * maxOpacity;
ctx.rotate(angleStep); // increase angle by step
ctx.globalAlpha = t; // set opacity to t
drawStripe(); // draw a small segment / "stripe"
}
// the other half of the cone is done with dark overlay
ctx.fillStyle = "rgb(95, 54, 5)";
for(angle = 0; angle < Math.PI; angle += angleStep) {
t = (angle < Math.PI * 0.5 ? angle : Math.PI - angle) / Math.PI * maxOpacity;;
ctx.rotate(angleStep);
ctx.globalAlpha = t;
drawStripe();
}
function drawStripe() {
ctx.beginPath();
ctx.lineTo(0, 0);
ctx.arc(0, 0, radius, 0, angleStep*5);
ctx.fill();
}
// top off by drawing a smaller circle on top
ctx.setTransform(1,0,0,1,0,0); // reset transforms
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "rgb(130, 97, 32)"; // draw in a topping
ctx.beginPath();
ctx.arc(cx, cy, radius * 0.25, 0, 2*Math.PI);
ctx.fill();
<canvas></canvas>
Example approximating a cone shape with shadows
var ctx = document.querySelector("canvas").getContext("2d"),
cx = 75, cy = 75, radius = 70, offset = radius * 2;
// draw base of cone
ctx.fillStyle = "rgb(139, 108, 33)";
ctx.arc(cx, cy, radius, 0, 2*Math.PI);
ctx.fill();
// offset next shape, couter-offset its shadow
ctx.translate(cx, offset*2); // make sure shape is drawn outside
ctx.scale(0.75, 1); // make shadow more narrow
ctx.globalCompositeOperation = "source-atop"; // comp. on top of existing pixels
ctx.shadowOffsetY = -offset * 1.1; // counter-offset shadow
ctx.shadowBlur = 25; // some blur
ctx.shadowColor = "rgba(181, 159, 109, 1)"; // highlight color
ctx.beginPath(); // draw new shape
ctx.arc(0, 0, radius * 0.6, 0, 2*Math.PI); // reduce radius ~50%
ctx.fill();
ctx.shadowOffsetY = -offset * 1.8; // counter-offset shadow
ctx.shadowColor = "rgba(95, 54, 5, 0.7)"; // shadow
ctx.beginPath(); // draw new shape
ctx.arc(0, 0, radius * 0.6, 0, 2*Math.PI); // reduce radius ~50%
ctx.fill();
// top off by drawing a smaller circle on top
ctx.setTransform(1,0,0,1,0,0); // reset transforms
ctx.globalCompositeOperation = "source-over"; // reset comp. mode
ctx.fillStyle = "rgb(130, 97, 32)"; // draw in a topping
ctx.beginPath();
ctx.arc(cx, cy, radius * 0.25, 0, 2*Math.PI);
ctx.fill();
<canvas></canvas>
Try using
ctx.shadowBlur = 40;
ctx.shadowColor = "#FF9900";
before drawing the circle.
shadowBlur sets the size of the shadow, you can set it to 0 if you want to disable it.
shadowColor is pretty self explanatory.
I want to create a radial gradient stroke around a circle,something similar to
Here is what I've tried so far,
var canvas = document.getElementById("analog-stopwatch");
var context = canvas.getContext('2d');
var cx=200, cy=200, radius=100;
context.beginPath();
context.arc(cx, cy, radius, 0, 2 * Math.PI, false);
// I want the ring width to be 20
var radialGradient = context.createRadialGradient(cx, cy, radius, cx, cy, radius+20);
radialGradient.addColorStop(0, "black");
radialGradient.addColorStop(1, "blue");
//context.lineWidth = 20;
context.strokeStyle = radialGradient;
context.stroke();
One way I could think of is creating a circle with radial gradient & clipping it with a concurrent circle of lesser radius, but is there any simpler way to achieve the same ?
There is a simpler way: draw the outer circle in one direction (ex:clockwise), then draw the inner circle with the other : it will be subtracted from the first path.
Then fill the resulting path :
fiddle :
http://jsbin.com/UCiCaYOn/1/edit?js,output
var canvas = document.getElementById("analog-stopwatch");
var context = canvas.getContext('2d');
var cx=200, cy=200, radius=100;
context.beginPath();
// !!!!
context.arc(cx, cy, radius+30, 0, 2 * Math.PI, false); // !!!
context.arc(cx, cy, radius, 0, 2 * Math.PI, true); // !!!
// !!!!
var radialGradient = context.createRadialGradient(cx, cy, radius, cx, cy, radius+20);
radialGradient.addColorStop(0, "black");
radialGradient.addColorStop(1, "blue");
context.fillStyle = radialGradient;
context.fill();
context.closePath();
Basically all you need to do is to change the rectangle a bit for the gradient and you're good (no need to stroke two arcs etc.):
var lineWidth = 20;
var radialGradient = context.createRadialGradient(cx, cy, radius-lineWidth*0.5,
cx, cy, radius+lineWidth*0.5);
Now you can stroke:
Updated fiddle here