How to make a pill shape in Canvas? (rounded corner rect basically) - html

Playing with a breakout clone and wanted to make rounded corner powerups.
Can someone point me in the right direction?
Thanks!

A simple way is to use quadraticCurveTo to smooth out corners
let roundRect = (ctx, x0, y0, x1, y1, r, color) => {
var w = x1 - x0;
var h = y1 - y0;
if (r > w/2) r = w/2;
if (r > h/2) r = h/2;
ctx.beginPath();
ctx.moveTo(x1 - r, y0);
ctx.quadraticCurveTo(x1, y0, x1, y0 + r);
ctx.lineTo(x1, y1-r);
ctx.quadraticCurveTo(x1, y1, x1 - r, y1);
ctx.lineTo(x0 + r, y1);
ctx.quadraticCurveTo(x0, y1, x0, y1 - r);
ctx.lineTo(x0, y0 + r);
ctx.quadraticCurveTo(x0, y0, x0 + r, y0);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
},
ctx = document.getElementById("canvas").getContext("2d");
roundRect(ctx, 50, 50, 150, 100, 5, "#F00");
roundRect(ctx, 50, 110, 100, 160, 10, "#00F");
roundRect(ctx, 53, 113, 97, 157, 7, "#0F0");
<canvas id="canvas" width="200" height="180"></canvas>

You can do what's shown on this article by Juan Mendes:
HTML:
<canvas id="rounded-rect" width="600" height="600">
<!-- Insert fallback content here -->
</canvas>​
JavaScript:
CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, radius, fill, stroke) {
if (typeof stroke == "undefined") {
stroke = true;
}
if (typeof radius === "undefined") {
radius = 5;
}
this.beginPath();
this.moveTo(x + radius, y);
this.lineTo(x + width - radius, y);
this.quadraticCurveTo(x + width, y, x + width, y + radius);
this.lineTo(x + width, y + height - radius);
this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
this.lineTo(x + radius, y + height);
this.quadraticCurveTo(x, y + height, x, y + height - radius);
this.lineTo(x, y + radius);
this.quadraticCurveTo(x, y, x + radius, y);
this.closePath();
if (stroke) {
this.stroke(stroke);
}
if (fill) {
this.fill(fill);
}
};
// Now you can just call
var ctx = document.getElementById("rounded-rect").getContext("2d");
// Manipulate it again
ctx.strokeStyle = "#2d6";
ctx.fillStyle = "#abc";
ctx.roundRect(100, 200, 200, 100, 50, true);
As you can see on this JsFiddle

Related

How to draw a rounded rectangle with one side open (no connecting line on left side) using HTML canvas?

I am trying to draw a rectangle (with three sides) which has rounded corners and no connecting line on the left hand side. For example, see below diagram (ignore imperfect hand drawing as the lengths are not drawn perfectly as parallel to each other):
This below example draws a full rectangle with rounded corners.
// Now you can just call
var ctx = document.getElementById("myCanvas").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {CanvasRenderingContext2D} ctx
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Number} [radius = 5] The corner radius; It can also be an object
* to specify different radii for corners
* #param {Number} [radius.tl = 0] Top left
* #param {Number} [radius.tr = 0] Top right
* #param {Number} [radius.br = 0] Bottom right
* #param {Number} [radius.bl = 0] Bottom left
* #param {Boolean} [fill = false] Whether to fill the rectangle.
* #param {Boolean} [stroke = true] Whether to stroke the rectangle.
*/
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
if (typeof stroke === 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
So far I have tried below code by modifying the above one, but not able to draw it successfully. The
bottom part is getting messed up with fold in the bottom left corner and creating a diagonal which is not required at all.
// Now you can just call
var ctx = document.getElementById("myCanvas").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {CanvasRenderingContext2D} ctx
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Number} [radius = 5] The corner radius; It can also be an object
* to specify different radii for corners
* #param {Number} [radius.tl = 0] Top left
* #param {Number} [radius.tr = 0] Top right
* #param {Number} [radius.br = 0] Bottom right
* #param {Number} [radius.bl = 0] Bottom left
* #param {Boolean} [fill = false] Whether to fill the rectangle.
* #param {Boolean} [stroke = true] Whether to stroke the rectangle.
*/
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
if (typeof stroke === 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {
tl: radius,
tr: radius,
br: radius,
bl: radius
};
} else {
var defaultRadius = {
tl: 0,
tr: 0,
br: 0,
bl: 0
};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
//ctx.lineTo(x + width, y + height - radius.br);
//ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x + width - radius.bl, y + height + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
Please help.
Thanks
The below solution worked for me as per my requirement :
// Now you can just call
var ctx = document.getElementById("myCanvas").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
if (typeof stroke === 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
// ctx.lineTo(x + radius.bl, y + height);
//ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.beginPath();
ctx.moveTo(x +width , y);
ctx.lineTo(x + radius.bl , y);
ctx.quadraticCurveTo(x, y , x, y + radius.bl);
ctx.lineTo(x, y+ height -radius.bl);
ctx.quadraticCurveTo(x , y + height , x + radius.bl , y + height );
ctx.lineTo(x+width, y + height);
//ctx.stroke();
//ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

Drawing shape on canvas with relative size

I have this code that draws a circle. How do I change this code so that the red circle is 100% of the browser window? I want the red circle to resize with the browser window.
<canvas width="100%" height="100%"></canvas>
var ctx;
function draw() {
ctx = $('canvas').get(0).getContext('2d');
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
}
function circle(x, y, r, c) {
ctx.beginPath();
var rad = ctx.createRadialGradient(x, y, 1, x, y, r);
rad.addColorStop(0, 'rgba('+c+',1)');
rad.addColorStop(1, 'rgba('+c+',0)');
ctx.fillStyle = rad;
ctx.arc(x, y, r, 0, Math.PI*2, false);
ctx.fill();
}
draw();
circle(128, 128, 200, '255,0,0');
consider this jsfiddle
on load/resize:
create the circle with draw() then setVars() then circle(...)
draw() (which sets the width/height of the canvas) will clear the canvas (see: How to clear the canvas for redrawing)
var ctx, canvas, x, y, w, h, r;
function draw() {
ctx = $('canvas').get(0).getContext('2d');
canvas = ctx.canvas;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function setVars() {
w = canvas.width;
h = canvas.height;
x = w/2;
y = h/2;
r = x < y ? x : y;
}
function circle(x, y, r, c) {
ctx.beginPath();
var rad = ctx.createRadialGradient(x, y, 1, x, y, r);
rad.addColorStop(0, 'rgba(' + c + ',1)');
rad.addColorStop(1, 'rgba(' + c + ',0)');
ctx.fillStyle = rad;
ctx.arc(x, y, r, 0, Math.PI * 2, false);
ctx.fill();
}
function makeCircle() {
draw();
setVars();
circle(x, y, r, '255,0,0');
}
$(window).resize(function () {
// redraw (onresize)
makeCircle();
});
// draw (onload)
makeCircle();

How to divide a circle into three equal parts with HTML5 canvas?

How can I divide a circle into three equal parts with HTML5 canvas 2D context API like above figure?
I was trying this
Can somebody suggest a better way? probably with percentages (or in degrees) instead of hard-coded coordinates?
var can = document.getElementById('mycanvas');
var ctx = can.getContext('2d');
ctx.fillStyle = "#BD1981";
ctx.beginPath();
ctx.arc(200, 200, 150, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "#FFC8B2";
ctx.lineWidth = "2";
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(100, 100);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(350, 200);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(100, 300);
ctx.closePath();
ctx.stroke();
Here is a function (demo) that allows you to specify a starting point, the length and the angle in degrees:
var drawAngledLine = function(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
Putting it all together (using #phant0m's drawAngledLine):
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var RADIUS = 70;
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawAngledLine(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
drawCircle(140, 140, RADIUS);
drawAngledLine(140, 140, RADIUS, 1 * (360 / 3));
drawAngledLine(140, 140, RADIUS, 2 * (360 / 3));
drawAngledLine(140, 140, RADIUS, 3 * (360 / 3));
Demo here:
http://jsfiddle.net/My8eX/
I know you probably got your answer but I found Wayne's jsfiddle helpful so I'm adding my contribution which lets you set a custom number of sections you want to divide the circle into.
http://jsfiddle.net/yorksea/3ef0y22c/2/
(also using #phant0m's drawAngledLine)
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var RADIUS = 300;
var num_sections = 19; //set this for number of divisions
function drawCircle(x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, 2 * Math.PI);
ctx.stroke();
}
function drawAngledLine(x, y, length, angle) {
var radians = angle / 180 * Math.PI;
var endX = x + length * Math.cos(radians);
var endY = y - length * Math.sin(radians);
ctx.beginPath();
ctx.moveTo(x, y)
ctx.lineTo(endX, endY);
ctx.closePath();
ctx.stroke();
}
//draw circle outline
drawCircle(320, 320, RADIUS);
//loop the number of sections to draw each
for (i = 1; i <= num_sections; i++) {
drawAngledLine(320, 320, RADIUS, i * (360 / num_sections));
}
<canvas id="canvas" width="650" height="650"></canvas>

Wrong rectangle size in canvas

I'm implementing a color picker. There is problem with the rendering. When I call c.fillRect(0, 0, 100, 80); the size of that rectangle is 103x42 px instead of 100x80. What is wrong here?
Also, rectangles are antialiased. Do I need offset the position by (0.5, 0.5) to avoid AA? I didn't use any kind of the coordinate system transformations.
colorSlider = function($e, color) {
this._$canvas = $('<canvas></canvas>');
this._c = this._$canvas[0].getContext('2d');
this._color = color || { r: 0, g: 0, b: 0 };
this._$canvas.width('310px');
this._$canvas.height('80px');
$e.append(this._$canvas);
this._render();
var me = this;
this._$canvas.mousedown(function(e) { me._mouseDown.call(me, e) });
this._$canvas.mouseup(function(e) { me._mouseUp.call(me, e) });
this._$canvas.mousemove(function(e) { me._mouseMove.call(me, e) });
this._dragChannel = 0;
}
colorSlider.prototype._pointInRect = function(x, y, rect) {
return x >= rect.x && x <= rect.x + rect.w && y >= rect.y && y <= rect.y + rect.h;
}
colorSlider.prototype._findTarget = function(event) {
var x = event.offsetX;
var y = event.offsetY;
console.log(x, y, this._rectR);
if (this._pointInRect(x, y, this._rectRThumb)) {
return { target: 1, value: x - this._rectR.x };
}
if (this._pointInRect(x, y, this._rectGThumb)) {
return { target: 2, value: x - this._rectG.x };
}
if (this._pointInRect(x, y, this._rectBThumb)) {
return { target: 3, value: x - this._rectB.x };
}
if (this._pointInRect(x, y, this._rectR)) {
return { target: 4, value: x - this._rectR.x };
}
if (this._pointInRect(x, y, this._rectG)) {
return { target: 5, value: x - this._rectG.x };
}
if (this._pointInRect(x, y, this._rectB)) {
return { target: 6, value: x - this._rectB.x };
}
return null;
}
colorSlider.prototype._mouseDown = function(event) {
this._dragChannel = 0;
var target = this._findTarget(event);
if (target) {
switch (target.target) {
case 1:
this._dragChannel = 1;
break;
case 2:
this._dragChannel = 2;
break;
case 3:
this._dragChannel = 3;
break;
case 4:
this._color.r = target.value;
break;
case 5:
this._color.g = target.value;
break;
case 6:
this._color.b = target.value;
break;
}
this._render();
}
};
colorSlider.prototype._mouseUp = function(event) {
//console.log('mouseUp');
};
colorSlider.prototype._mouseMove = function(event) {
//console.log('mouseMove', event);
};
colorSlider.prototype.padding = 4;
colorSlider.prototype._render = function() {
var padding = this.padding;
var thickness = 16;
var c = this._c;
var w = 255;
var h = this._$canvas.height();
c.clearRect(0, 0, this._$canvas.width(), this._$canvas.height());
var gradient = c.createLinearGradient(padding, 0, w, 0);
c.fillStyle = gradient;
gradient.addColorStop(0, this.colorToHex({ r: 0, g: this._color.g, b: this._color.b }));
gradient.addColorStop(1, this.colorToHex({ r: 255, g: this._color.g, b: this._color.b }));
c.fillRect(padding, padding, w, thickness);
c.lineWidth = 0;
c.fillRect(0, 0, 100, 80);
this._rectR = { x: padding, y: padding, w: w, h: thickness };
gradient = c.createLinearGradient(padding, 0, w, 0);
c.fillStyle = gradient;
gradient.addColorStop(0, this.colorToHex({ r: this._color.r, g: 0, b: this._color.b }));
gradient.addColorStop(1, this.colorToHex({ r: this._color.r, g: 255, b: this._color.b }));
c.fillRect(padding, padding + thickness + 2 * padding, w, thickness);
this._rectG = { x: padding, y: padding + thickness + 2 * padding, w: w, h: thickness };
gradient = c.createLinearGradient(padding, 0, w, 0);
c.fillStyle = gradient;
gradient.addColorStop(0, this.colorToHex({ r: this._color.r, g: this._color.g, b: 0 }));
gradient.addColorStop(1, this.colorToHex({ r: this._color.r, g: this._color.g, b: 255 }));
c.fillRect(padding, padding + 2 * (thickness + 2 * padding), w, thickness);
this._rectB = { x: padding, y: padding + 2 * (thickness + 2 * padding), w: w, h: thickness };
c.lineWidth = 2;
c.fillStyle = "white";
c.strokeStyle = "#888888";
this._rectRThumb = { x: padding + this._color.r - 2, y: padding / 2, w: 8, h: 20, r: 2 };
this.drawRoundedRectangle(c, this._rectRThumb);
this._rectGThumb = { x: padding + this._color.g - 2, y: padding / 2 + 2 * padding + thickness, w: 8, h: 20, r: 2 };
this.drawRoundedRectangle(c, this._rectGThumb);
this._rectBThumb = { x: padding + this._color.b - 2, y: padding / 2 + 2 * (2 * padding + thickness), w: 8, h: 20, r: 2 };
this.drawRoundedRectangle(c, this._rectBThumb);
};
colorSlider.prototype.colorToHex = function(color) {
var c = '#'
+ (color.r + 256).toString(16).substr(1, 2)
+ (color.g + 256).toString(16).substr(1, 2)
+ (color.b + 256).toString(16).substr(1, 2);
console.log(c);
return c;
};
// http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
colorSlider.prototype.drawRoundedRectangle = function(c, rect) {
var x = rect.x;
var y = rect.y;
var width = rect.w;
var height = rect.h;
var radius = rect.r;
c.beginPath();
c.moveTo(x + radius, y);
c.lineTo(x + width - radius, y);
c.quadraticCurveTo(x + width, y, x + width, y + radius);
c.lineTo(x + width, y + height - radius);
c.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
c.lineTo(x + radius, y + height);
c.quadraticCurveTo(x, y + height, x, y + height - radius);
c.lineTo(x, y + radius);
c.quadraticCurveTo(x, y, x + radius, y);
c.closePath();
c.stroke();
c.fill();
};
index.html
<script>
$(function() {
$("#directionalLight,#ambientLight").each(function() {
new colorSlider($(this));
});
});
</script>
<body>
<div>Directional light</div>
<div id="directionalLight"></div>
<div>Ambient light</div>
<div id="ambientLight"></div>
</body>
The first thing to know is that a canvas element has intrinsic dimensions = number of pixels in the inside coordinate space (set by the width and height attributes and properties). It also has extrinsic dimensions (style.width and style.height) which is the number of pixels that the image takes within the webpage. The intrinsic pixels are scaled to fit the extrinsic space.
It's confusing because an img also has intrinsic and extrinsic dimensions, but the names of the properties are completely different from canvas. If you set width and height on an image, it's basically the same as setting style.width or style.height; they both set the extrinsic dimensions to scale the image within the page. Meanwhile, you can only get the intrinsic dimensions of an img using the new naturalWidth and naturalHeight (HTML5 browsers only) properties.
If the extrinsic dimensions are not set on both img and canvas, the image will be laid out at the same size as the intrinsic dimensions (i.e., scale factor would be 1).
Now, when you use jQuery, $(canvas).width('310px') is the same as $(canvas).css('310px'), which sets the extrinsic dimensions. You have to call $(canvas).prop('width', 310) or simply set canvas.width = 310 to set the intrinsic width.

How to draw a rounded rectangle using HTML Canvas?

HTML Canvas provides methods for drawing rectangles, fillRect() and strokeRect(), but I can't find a method for making rectangles with rounded corners. How can I do that?
Nowadays you can just use context.roundRect. See further details on Kaiido's answer
var ctx = document.getElementById("rounded-rect").getContext("2d");
ctx.beginPath();
// Draw using 5px for border radius on all sides
// stroke it but no fill
ctx.roundRect(5, 5, 50, 50, 5);
ctx.stroke();
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
ctx.beginPath();
ctx.roundRect(100, 5, 100, 100, 20);
ctx.stroke();
ctx.fill();
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, top-left clockwise to bottom-left
ctx.beginPath();
ctx.roundRect(300, 5, 200, 100, [50,0,25,0]);
ctx.fill();
ctx.stroke();
<canvas id="rounded-rect" width="500" height="200">
<!-- Insert fallback content here -->
</canvas>
Old answer:
I needed to do the same thing and created a method to do it.
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {CanvasRenderingContext2D} ctx
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Number} [radius = 5] The corner radius; It can also be an object
* to specify different radii for corners
* #param {Number} [radius.tl = 0] Top left
* #param {Number} [radius.tr = 0] Top right
* #param {Number} [radius.br = 0] Bottom right
* #param {Number} [radius.bl = 0] Bottom left
* #param {Boolean} [fill = false] Whether to fill the rectangle.
* #param {Boolean} [stroke = true] Whether to stroke the rectangle.
*/
function roundRect(
ctx,
x,
y,
width,
height,
radius = 5,
fill = false,
stroke = true
) {
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
radius = {...{tl: 0, tr: 0, br: 0, bl: 0}, ...radius};
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
}
// Now you can just call
var ctx = document.getElementById("rounded-rect").getContext("2d");
// Draw using default border radius,
// stroke it but no fill (function's default values)
roundRect(ctx, 5, 5, 50, 50);
// To change the color on the rectangle, just manipulate the context
ctx.strokeStyle = "rgb(255, 0, 0)";
ctx.fillStyle = "rgba(255, 255, 0, .5)";
roundRect(ctx, 100, 5, 100, 100, 20, true);
// Manipulate it again
ctx.strokeStyle = "#0f0";
ctx.fillStyle = "#ddd";
// Different radii for each corner, others default to 0
roundRect(ctx, 300, 5, 200, 100, {
tl: 50,
br: 25
}, true);
<canvas id="rounded-rect" width="500" height="200">
<!-- Insert fallback content here -->
</canvas>
Different radii per corner provided by Corgalore
See http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html
for further explanation
I started with #jhoff's solution, but rewrote it to use width/height parameters, and using arcTo makes it quite a bit more terse:
CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.beginPath();
this.moveTo(x+r, y);
this.arcTo(x+w, y, x+w, y+h, r);
this.arcTo(x+w, y+h, x, y+h, r);
this.arcTo(x, y+h, x, y, r);
this.arcTo(x, y, x+w, y, r);
this.closePath();
return this;
}
Also returning the context so you can chain a little. E.g.:
ctx.roundRect(35, 10, 225, 110, 20).stroke(); //or .fill() for a filled rect
The HTML5 canvas doesn't provide a method to draw a rectangle with rounded corners.
How about using the lineTo() and arc() methods?
You can also use the quadraticCurveTo() method instead of the arc() method.
Juan, I made a slight improvement to your method to allow for changing each rectangle corner radius individually:
/**
* Draws a rounded rectangle using the current state of the canvas.
* If you omit the last three params, it will draw a rectangle
* outline with a 5 pixel border radius
* #param {Number} x The top left x coordinate
* #param {Number} y The top left y coordinate
* #param {Number} width The width of the rectangle
* #param {Number} height The height of the rectangle
* #param {Object} radius All corner radii. Defaults to 0,0,0,0;
* #param {Boolean} fill Whether to fill the rectangle. Defaults to false.
* #param {Boolean} stroke Whether to stroke the rectangle. Defaults to true.
*/
CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, fill, stroke) {
var cornerRadius = { upperLeft: 0, upperRight: 0, lowerLeft: 0, lowerRight: 0 };
if (typeof stroke == "undefined") {
stroke = true;
}
if (typeof radius === "object") {
for (var side in radius) {
cornerRadius[side] = radius[side];
}
}
this.beginPath();
this.moveTo(x + cornerRadius.upperLeft, y);
this.lineTo(x + width - cornerRadius.upperRight, y);
this.quadraticCurveTo(x + width, y, x + width, y + cornerRadius.upperRight);
this.lineTo(x + width, y + height - cornerRadius.lowerRight);
this.quadraticCurveTo(x + width, y + height, x + width - cornerRadius.lowerRight, y + height);
this.lineTo(x + cornerRadius.lowerLeft, y + height);
this.quadraticCurveTo(x, y + height, x, y + height - cornerRadius.lowerLeft);
this.lineTo(x, y + cornerRadius.upperLeft);
this.quadraticCurveTo(x, y, x + cornerRadius.upperLeft, y);
this.closePath();
if (stroke) {
this.stroke();
}
if (fill) {
this.fill();
}
}
Use it like this:
var canvas = document.getElementById("canvas");
var c = canvas.getContext("2d");
c.fillStyle = "blue";
c.roundRect(50, 100, 50, 100, {upperLeft:10,upperRight:10}, true, true);
Good news everyone!
roundRect(x, y, width, height, radii); is now officially part of the Canvas 2D API.
It is exposed on CanvasRenderingContext2D, Path2D and OffscreenCanvasRenderingContext2D objects.
Its radii parameter is an Array which contains either
a single float, representing the radius to use for all four corners,
two floats, for the top-left + bottom-right and top-right + bottom-left corners respectively,
three floats, for the top-left, top-right + bottom-left and bottom-right respectively,
or four floats, one per corner,
OR the same combinations, but with a DOMPointInit object, representing the x-radius and y-radius of each corner.
Currently, only Chrome has an implementation available, but you can find a polyfill I made, in this repo.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.roundRect(20,20,80,80,[new DOMPoint(60,80), new DOMPoint(110,100)]);
ctx.strokeStyle = "green";
ctx.stroke();
const path = new Path2D();
path.roundRect(120,30,60,90,[0,25,new DOMPoint(60,80), new DOMPoint(110,100)]);
ctx.fillStyle = "purple";
ctx.fill(path);
// and a simple one
ctx.beginPath();
ctx.roundRect(200,20,80,80,[10]);
ctx.fillStyle = "orange";
ctx.fill();
<script src="https://cdn.jsdelivr.net/gh/Kaiido/roundRect#main/roundRect.js"></script>
<canvas></canvas>
This code creates a 100-pixel square, with rounded corners of 30 pixels.
var canvas = document.createElement("canvas");
document.body.appendChild(canvas);
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(100,100);
ctx.arcTo(0,100,0,0,30);
ctx.arcTo(0,0,100,0,30);
ctx.arcTo(100,0,100,100,30);
ctx.arcTo(100,100,0,100,30);
ctx.fill();
The drawPolygon function below can be used to draw any polygon with rounded corners.
See it running here.
function drawPolygon(ctx, pts, radius) {
if (radius > 0) {
pts = getRoundedPoints(pts, radius);
}
var i, pt, len = pts.length;
ctx.beginPath();
for (i = 0; i < len; i++) {
pt = pts[i];
if (i == 0) {
ctx.moveTo(pt[0], pt[1]);
} else {
ctx.lineTo(pt[0], pt[1]);
}
if (radius > 0) {
ctx.quadraticCurveTo(pt[2], pt[3], pt[4], pt[5]);
}
}
ctx.closePath();
}
function getRoundedPoints(pts, radius) {
var i1, i2, i3, p1, p2, p3, prevPt, nextPt,
len = pts.length,
res = new Array(len);
for (i2 = 0; i2 < len; i2++) {
i1 = i2-1;
i3 = i2+1;
if (i1 < 0) {
i1 = len - 1;
}
if (i3 == len) {
i3 = 0;
}
p1 = pts[i1];
p2 = pts[i2];
p3 = pts[i3];
prevPt = getRoundedPoint(p1[0], p1[1], p2[0], p2[1], radius, false);
nextPt = getRoundedPoint(p2[0], p2[1], p3[0], p3[1], radius, true);
res[i2] = [prevPt[0], prevPt[1], p2[0], p2[1], nextPt[0], nextPt[1]];
}
return res;
};
function getRoundedPoint(x1, y1, x2, y2, radius, first) {
var total = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
idx = first ? radius / total : (total - radius) / total;
return [x1 + (idx * (x2 - x1)), y1 + (idx * (y2 - y1))];
};
The function receives an array with the polygon points, like this:
var canvas = document.getElementById("cv");
var ctx = canvas.getContext("2d");
ctx.strokeStyle = "#000000";
ctx.lineWidth = 5;
drawPolygon(ctx, [[20, 20],
[120, 20],
[120, 120],
[ 20, 120]], 10);
ctx.stroke();
This is a port and a more generic version of a solution posted here.
Here's one I wrote... uses arcs instead of quadratic curves for better control over radius. Also, it leaves the stroking and filling up to you
/* Canvas 2d context - roundRect
*
* Accepts 5 parameters:
the start_x,
start_y points,
the end_x,
end_y points,
the radius of the corners
*
* No return value
*/
CanvasRenderingContext2D.prototype.roundRect = function(sx,sy,ex,ey,r) {
var r2d = Math.PI/180;
if( ( ex - sx ) - ( 2 * r ) < 0 ) { r = ( ( ex - sx ) / 2 ); } //ensure that the radius isn't too large for x
if( ( ey - sy ) - ( 2 * r ) < 0 ) { r = ( ( ey - sy ) / 2 ); } //ensure that the radius isn't too large for y
this.beginPath();
this.moveTo(sx+r,sy);
this.lineTo(ex-r,sy);
this.arc(ex-r,sy+r,r,r2d*270,r2d*360,false);
this.lineTo(ex,ey-r);
this.arc(ex-r,ey-r,r,r2d*0,r2d*90,false);
this.lineTo(sx+r,ey);
this.arc(sx+r,ey-r,r,r2d*90,r2d*180,false);
this.lineTo(sx,sy+r);
this.arc(sx+r,sy+r,r,r2d*180,r2d*270,false);
this.closePath();
}
Here is an example:
var _e = document.getElementById('#my_canvas');
var _cxt = _e.getContext("2d");
_cxt.roundRect(35,10,260,120,20);
_cxt.strokeStyle = "#000";
_cxt.stroke();
So this is based out of using lineJoin="round" and with the proper proportions, mathematics and logic I have been able to make this function, this is not perfect but hope it helps. If you want to make each corner have a different radius take a look at: https://p5js.org/reference/#/p5/rect
Here ya go:
CanvasRenderingContext2D.prototype.roundRect = function (x,y,width,height,radius) {
radius = Math.min(Math.max(width-1,1),Math.max(height-1,1),radius);
var rectX = x;
var rectY = y;
var rectWidth = width;
var rectHeight = height;
var cornerRadius = radius;
this.lineJoin = "round";
this.lineWidth = cornerRadius;
this.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.fillRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.stroke();
this.fill();
}
CanvasRenderingContext2D.prototype.roundRect = function (x,y,width,height,radius) {
radius = Math.min(Math.max(width-1,1),Math.max(height-1,1),radius);
var rectX = x;
var rectY = y;
var rectWidth = width;
var rectHeight = height;
var cornerRadius = radius;
this.lineJoin = "round";
this.lineWidth = cornerRadius;
this.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.fillRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
this.stroke();
this.fill();
}
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
function yop() {
ctx.clearRect(0,0,1000,1000)
ctx.fillStyle = "#ff0000";
ctx.strokeStyle = "#ff0000"; ctx.roundRect(Number(document.getElementById("myRange1").value),Number(document.getElementById("myRange2").value),Number(document.getElementById("myRange3").value),Number(document.getElementById("myRange4").value),Number(document.getElementById("myRange5").value));
requestAnimationFrame(yop);
}
requestAnimationFrame(yop);
<input type="range" min="0" max="1000" value="10" class="slider" id="myRange1"><input type="range" min="0" max="1000" value="10" class="slider" id="myRange2"><input type="range" min="0" max="1000" value="200" class="slider" id="myRange3"><input type="range" min="0" max="1000" value="100" class="slider" id="myRange4"><input type="range" min="1" max="1000" value="50" class="slider" id="myRange5">
<canvas id="myCanvas" width="1000" height="1000">
</canvas>
Here's a solution using the lineJoin property to round the corners. It works if you just need a solid shape, but not so much if you need a thin border that's smaller than the border radius.
function roundedRect(ctx, options) {
ctx.strokeStyle = options.color;
ctx.fillStyle = options.color;
ctx.lineJoin = "round";
ctx.lineWidth = options.radius;
ctx.strokeRect(
options.x+(options.radius*.5),
options.y+(options.radius*.5),
options.width-options.radius,
options.height-options.radius
);
ctx.fillRect(
options.x+(options.radius*.5),
options.y+(options.radius*.5),
options.width-options.radius,
options.height-options.radius
);
ctx.stroke();
ctx.fill();
}
const canvas = document.getElementsByTagName("canvas")[0];
const ctx = canvas.getContext("2d");
roundedRect(ctx, {
x: 10,
y: 10,
width: 200,
height: 100,
radius: 35,
color: "red"
});
<canvas></canvas>
Opera, ffs.
if (window["CanvasRenderingContext2D"]) {
/** #expose */
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
if (w < 2*r) r = w/2;
if (h < 2*r) r = h/2;
this.beginPath();
if (r < 1) {
this.rect(x, y, w, h);
} else {
if (window["opera"]) {
this.moveTo(x+r, y);
this.arcTo(x+r, y, x, y+r, r);
this.lineTo(x, y+h-r);
this.arcTo(x, y+h-r, x+r, y+h, r);
this.lineTo(x+w-r, y+h);
this.arcTo(x+w-r, y+h, x+w, y+h-r, r);
this.lineTo(x+w, y+r);
this.arcTo(x+w, y+r, x+w-r, y, r);
} else {
this.moveTo(x+r, y);
this.arcTo(x+w, y, x+w, y+h, r);
this.arcTo(x+w, y+h, x, y+h, r);
this.arcTo(x, y+h, x, y, r);
this.arcTo(x, y, x+w, y, r);
}
}
this.closePath();
};
/** #expose */
CanvasRenderingContext2D.prototype.fillRoundRect = function(x, y, w, h, r) {
this.roundRect(x, y, w, h, r);
this.fill();
};
/** #expose */
CanvasRenderingContext2D.prototype.strokeRoundRect = function(x, y, w, h, r) {
this.roundRect(x, y, w, h, r);
this.stroke();
};
}
Since Opera is going WebKit, this should also remain valid in the legacy case.
To make the function more consistent with the normal means of using a canvas context, the canvas context class can be extended to include a 'fillRoundedRect' method -- that can be called in the same way fillRect is called:
var canv = document.createElement("canvas");
var cctx = canv.getContext("2d");
// If thie canvasContext class doesn't have a fillRoundedRect, extend it now
if (!cctx.constructor.prototype.fillRoundedRect) {
// Extend the canvaseContext class with a fillRoundedRect method
cctx.constructor.prototype.fillRoundedRect =
function (xx,yy, ww,hh, rad, fill, stroke) {
if (typeof(rad) == "undefined") rad = 5;
this.beginPath();
this.moveTo(xx+rad, yy);
this.arcTo(xx+ww, yy, xx+ww, yy+hh, rad);
this.arcTo(xx+ww, yy+hh, xx, yy+hh, rad);
this.arcTo(xx, yy+hh, xx, yy, rad);
this.arcTo(xx, yy, xx+ww, yy, rad);
if (stroke) this.stroke(); // Default to no stroke
if (fill || typeof(fill)=="undefined") this.fill(); // Default to fill
}; // end of fillRoundedRect method
}
The code checks to see if the prototype for the constructor for the canvas context object contains a 'fillRoundedRect' property and adds one -- the first time around. It is invoked in the same manner as the fillRect method:
ctx.fillStyle = "#eef"; ctx.strokeStyle = "#ddf";
// ctx.fillRect(10,10, 200,100);
ctx.fillRoundedRect(10,10, 200,100, 5);
The method uses the arcTo method as Grumdring did. In the method, this is a reference to the ctx object. The stroke argument defaults to false if undefined. The fill argument defaults to fill the rectangle if undefined.
(Tested on Firefox, I don't know if all implementations permit extension in this manner.)
Method 1: Using path-drawing methods
The most straightforward method of doing this with HTML Canvas is by using the path-drawing methods of ctx:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(ctx, x, y, width, height, radius) {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
}
ctx.fillStyle = "red";
roundedRect(ctx, 10, 10, 100, 100, 20);
ctx.fill();
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
Method 2: Using Path2D
You can also draw rounded rectangles in HTML Canvas by using the Path2D interface:
Example 1
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(x, y, width, height, radius) {
return new Path2D(`M ${x + radius} ${y} H ${x + width - radius} a ${radius} ${radius} 0 0 1 ${radius} ${radius} V ${y + height - radius} a ${radius} ${radius} 0 0 1 ${-radius} ${radius} H ${x + radius} a ${radius} ${radius} 0 0 1 ${-radius} ${-radius} V ${y + radius} a ${radius} ${radius} 0 0 1 ${radius} ${-radius}`);
}
ctx.fillStyle = "blue";
ctx.fill(roundedRect(10, 10, 100, 100, 20));
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
Example 2
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
function roundedRect(x, y, width, height, radius) {
let path = new Path2D();
path.moveTo(x + radius, y);
path.lineTo(x + width - radius, y);
path.quadraticCurveTo(x + width, y, x + width, y + radius);
path.lineTo(x + width, y + height - radius);
path.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
path.lineTo(x + radius, y + height);
path.quadraticCurveTo(x, y + height, x, y + height - radius);
path.lineTo(x, y + radius);
path.quadraticCurveTo(x, y, x + radius, y);
path.closePath();
return path;
}
ctx.fillStyle = "green";
ctx.fill(roundedRect(10, 10, 100, 100, 20));
<canvas id="canvas">
<!-- Fallback content -->
</canvas>
try to add this line , when you want to get rounded corners : ctx.lineCap = "round";
NONE of the other answers can handle the following 3 cases correctly:
if ((width >= radius x 2) && (height <= radius * 2))
if ((width <= radius x 2) && (height >= radius * 2))
if ((width <= radius x 2) && (height <= radius * 2))
If any of these cases happen, you will not get a correctly drawn rectangle
My Solution handles ANY radius and ANY Width and Height dynamically, and should be the default answer
function roundRect(ctx, x, y, width, height, radius) {
/*
* Draws a rounded rectangle using the current state of the canvas.
*/
let w = width;
let h = height;
let r = radius;
ctx.stroke()
ctx.fill()
ctx.beginPath();
// Configure the roundedness of the rectangles corners
if ((w >= r * 2) && (h >= r * 2)) {
// Handles width and height larger than diameter
// Keep radius fixed
ctx.moveTo(x + r, y); // tr start
ctx.lineTo(x + w - r, y); // tr
ctx.quadraticCurveTo(x + w, y, x + w, y + r); //tr
ctx.lineTo(x + w, y + h - r); // br
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); // br
ctx.lineTo(x + r, y + h); // bl
ctx.quadraticCurveTo(x, y + h, x, y + h - r); // bl
ctx.lineTo(x, y + r); // tl
ctx.quadraticCurveTo(x, y, x + r, y); // tl
} else if ((w < r * 2) && (h > r * 2)) {
// Handles width lower than diameter
// Radius must dynamically change as half of width
r = w / 2;
ctx.moveTo(x + w, y + h - r); // br start
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); // br curve
ctx.quadraticCurveTo(x, y + h, x, y + h - r) // bl curve
ctx.lineTo(x, y + r); // line
ctx.quadraticCurveTo(x, y, x + r, y); // tl
ctx.quadraticCurveTo(x + w, y, x + w, y + r); // tl
ctx.lineTo(x + w, y + h - r); // line
} else if ((w > r * 2) && (h < r * 2)) {
// Handles height lower than diameter
// Radius must dynamically change as half of height
r = h / 2;
ctx.moveTo(x + w - r, y + h); // br start
ctx.quadraticCurveTo(x + w, y + h, x + w, y + r); // br curve
ctx.quadraticCurveTo(x + w, y, x + w - r, y); // tr curve
ctx.lineTo(x + r, y); // line between tr tl
ctx.quadraticCurveTo(x, y, x, y + r); // tl curve
ctx.quadraticCurveTo(x, y + h, x + r, y + h); // bl curve
} else if ((w < 2 * r) && (h < 2 * r)) {
// Handles width and height lower than diameter
ctx.moveTo(x + w / 2, y + h);
ctx.quadraticCurveTo(x + w, y + h, x + w, y + h / 2); // bl curve
ctx.quadraticCurveTo(x + w, y, x + w / 2, y); // tr curve
ctx.quadraticCurveTo(x, y, x, y + h / 2); // tl curve
ctx.quadraticCurveTo(x, y + h, x + w / 2, y + h); // bl curve
}
ctx.closePath();
}