199

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?

mfluehr
  • 2,832
  • 2
  • 23
  • 31
DNB5brims
  • 29,344
  • 50
  • 131
  • 195
  • 4
    This guy put how to make rounded rectangles (filled, with border...) in a nice method: http://js-bits.blogspot.com/2010/07/canvas-rounded-corner-rectangles.html – nerdess May 26 '11 at 16:51
  • https://stackoverflow.com/a/44856925/62255 – jedierikb Dec 07 '18 at 16:01

15 Answers15

435

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 for browsers that don't support roundRect

As of April 10, 2023, All major browser support it in their latest releases.

See https://caniuse.com/mdn-api_canvasrenderingcontext2d_roundrect


I needed the same thing and created a function for 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>
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • 9
    Perfect answer... How is this not native to canvas yet?! Thanks. – andygoestohollywood Nov 14 '13 at 12:00
  • 2
    the code has a bug, it needs to do stroke AFTER fill, otherwise on small rectangles the fill will overwrite the stroke. – Zig Mandel Jun 10 '14 at 00:03
  • 2
    I dont have the example at hand but I had to modify that order for a case I tested on my code. Its logical, how can it correctly stroke (with smoothing using the rect background color) if you havent yet filled the rect? – Zig Mandel Jul 22 '14 at 23:00
  • 2
    @Juan hey my bad, I noticed the blog post and caught that tidbit after. I meant to undo the edit. Goodjob man +1'd you! – MrPizzaFace Jan 23 '15 at 22:34
  • 6
    Zig Mandel is correct: it should be filled and then stroked. The reason is that if you stroke and then fill then the line width is halved. Try it with a really thick line width (say, 20) and compare a rounded rectangle which is filled with the background colour to a rounded rectangle which is not filled. The line width of the filled one will be half of the line width of the not filled one. – Andrew Stacey Apr 16 '15 at 20:47
  • took it as a reference ... saved me! thnaks – Jony-Y Dec 27 '16 at 14:48
  • there is a minor issue with it: the stroke() method draws a line in the middle of where you use it. so if you want to stroke a box with x = 100 and a ctx.lineWidth = 40 for example then the rounded corners will be drawn at x - lineWidth / 2 = 80; which will offset where you wanted to render it and that can cause problems. – Daniel Bengtsson Apr 10 '18 at 02:57
  • untested, but something like this: var strokeWidth = ctx.lineWidth; x = x + Math.floor(strokeWidth / 2); y = y + Math.floor(strokeWidth / 2); in the begining of the function should align it properly and make it draw where you want to. – Daniel Bengtsson Apr 10 '18 at 03:02
  • Daniel, can you create a test case at jsfiddle or a similar site and I can fix this answer? – Ruan Mendes Apr 10 '18 at 19:55
  • Notice: if you use radius > 0 and negative width or height, you will get modern art (not rectangle)... – ViliusL Jul 27 '18 at 13:37
  • I think you need one more equal sign on this line `if (typeof stroke == 'undefined') {` – igobivo Sep 19 '19 at 05:07
  • @igobivo Technically you don't. typeof can only return a string, this is a preference issue and not really related to the problem itself. Having said that, I will fix it since it's inconsistent :p – Ruan Mendes Sep 19 '19 at 18:20
  • 1
    Fantastic solution, thanks very much. Here's my jsfiddle: https://jsfiddle.net/raddevus/taczyvdm/27/ – raddevus Nov 10 '20 at 21:43
  • in round corner relate to sides of shape is dirty graphics, can handle that? – S At Jul 30 '21 at 21:32
  • @SAt Hard to tell what you're having problems with. I recommend you ask a new question with details and a running example – Ruan Mendes Dec 06 '21 at 17:56
  • 2
    **Firefox does not support it** (yet): https://caniuse.com/mdn-api_canvasrenderingcontext2d_roundrect or https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/roundRect#browser_compatibility – handle Jan 20 '23 at 21:32
  • neither does safari – Alice Apr 01 '23 at 20:38
  • roundRect is not supported on iOS safari* – Alice Apr 12 '23 at 07:09
  • @Alice [caniuse](https://caniuse.com/mdn-api_canvasrenderingcontext2d_roundrect) says it's supported – Ruan Mendes Apr 12 '23 at 07:29
  • 1
    The problem with iOS safari is depending on the model of apple device you have you can't update safari past a certain version. Even on iOS 15 this is not supported https://i.imgur.com/0QjJ5mQ.png Which leads me to conclude that this method is not production-ready quite yet. Especially given firefox doesn't support it across the board. My 2020 iPad Pro is still using a safari version that doesn't support this method. – Alice Apr 14 '23 at 05:53
143

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
lepe
  • 24,677
  • 9
  • 99
  • 108
Grumdrig
  • 16,588
  • 14
  • 58
  • 69
  • 5
    I wouldn't mess with the Canvas rendering context, except for that good solution. – Ash Blue May 29 '12 at 20:06
  • The problem with this solution is that you can't control the radius for each corner independently. Not flexible enough. See my solution below. – Corgalore Aug 18 '12 at 21:14
  • 1
    This is a centered rectangle, if somebody needs one with its top left corner in `(x,y)`, save the context, add a translation to `(-w/2,-h/2)`, and restore the context. – nessa.gp Nov 11 '14 at 17:34
  • Thank you, this is the only one that has worked for me thus far, the others giving me issues when the radius was greater or larger than the height or width. Implemented! – Howzieky Oct 10 '16 at 02:53
  • 1
    Note that this solution works to make any polygon have rounded corners. A [fiddle](https://jsfiddle.net/doguleez/g082u6oy/). – Doguleez Jun 27 '17 at 22:23
  • Neat solution, but why are you modifying the prototype. It is tidy I guess, but is it convention or anything? – agiopnl Dec 14 '21 at 14:35
  • @Howzieky why would you wanna try a greater radius than the widt, it's impossible. – agiopnl Dec 14 '21 at 14:37
  • @Doguleez how is it supposed to work for every polygon without being totally rewritten? – agiopnl Dec 14 '21 at 14:37
  • 1
    @nessa.gp it is not centered, you probobly misread the top two lines. – agiopnl Dec 14 '21 at 14:39
56

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.

Coder-256
  • 5,212
  • 2
  • 23
  • 51
  • For some reason, I seem to be having issues with arcTo in Firefox 3.5 and Opera 10.0. Similar to this site: http://ditchnet.org/canvas/CanvasRoundedCornerExample.html – bgw Dec 10 '09 at 00:31
  • arcTo has been fixed in the latest version of FF. – Ash Blue May 29 '12 at 20:04
  • 7
    Can you provide an example? – Jean-Pierre Bécotte May 11 '18 at 15:37
  • As of 2022, there is a specification which might be available soon or later. See [Woold answer](https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-using-html-canvas/68359160#68359160). – lepe May 01 '22 at 13:33
35

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);
lepe
  • 24,677
  • 9
  • 99
  • 108
Corgalore
  • 2,486
  • 2
  • 23
  • 32
  • 2
    This approach provides so much control over the rounded corners. Why is this not the accepted answer> – vighnesh153 Nov 10 '19 at 15:27
  • 2
    @VighneshRaut Probably because this answer copy/pasted the original accepted answer and added rounded corners. I incorporated it into the accepted answer gave credit to this answer. The accepted answer has a live example and the syntax is simpler if you do want all corners with the same radius (which is the most common case). Lastly, this answer suggest modifying the prototype of a native object which is a no-no – Ruan Mendes Jul 15 '20 at 16:15
26

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 either

  • a single float, representing the radius to use for all four corners,
  • an Array which contains either such a single float,
  • or two floats, for the top-left + bottom-right and top-right + bottom-left corners respectively,
  • or 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 DOMPointInit objects (any object with a float x and y property), representing the x-radius and y-radius of each corner and allowing to use arcs of ellipse instead of "just" arcs of circle.

It is now supported in every main browser, and, for older browsers, 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>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 1
    is this ever gonna happen? – swisswiss Aug 16 '21 at 00:14
  • 1
    @swisswiss it did happen already. It's part of the specs, Chrome has (partial) support. – Kaiido Aug 16 '21 at 00:48
  • @Kaiido i cannot find in which version of chrome is this supported, is not even in documented in MDN – Coding Edgar Sep 27 '21 at 15:27
  • 1
    @CodingEdgar ah, it seems they still do hide it under the Experimental Web Platform flag. For MDN, it's ready to be uploaded at https://github.com/fserb/canvas2D/pull/18/files#diff-cb4343e566a25a1b9180e945317acd744715cf048961b61434d375198bfb58e3 – Kaiido Sep 27 '21 at 23:10
  • Firefox does not support it as in the last day of 2022. – Jay Wang Jan 01 '23 at 01:25
  • 1
    @JayWong yes it's in the answer: "Currently, only Chrome has an implementation available, but you can find a polyfill I made, in [this repo](https://github.com/Kaiido/roundRect).". I will update it when browsers support changes. – Kaiido Jan 01 '23 at 01:36
16

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.

lepe
  • 24,677
  • 9
  • 99
  • 108
moraes
  • 13,213
  • 7
  • 45
  • 59
16

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();
mfluehr
  • 2,832
  • 2
  • 23
  • 31
atom
  • 692
  • 7
  • 11
13

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();
lepe
  • 24,677
  • 9
  • 99
  • 108
jhoff
  • 716
  • 7
  • 14
  • How does this give you better control over radius? I thought you were going to allow for x/y radii (oval corners), and also specifying different radii for each corner – Ruan Mendes Jun 29 '11 at 19:59
  • 3
    Your `r2d` probably wants to be called `d2r`. – Grumdrig Oct 20 '11 at 16:07
  • 2
    @JuanMendes: The (arc-based) shapes of the rounded corners in this solution are more circular than those of your (quadratic-based) solution. I think that is what he meant by "better control over radius". – Brent Bradburn Jan 27 '13 at 06:06
  • I also used this method, it is better than using quadraticCurve. But if you draw something more complex than rectangle it is REALLY painful. With there was an automatic method like in Android canvas. – Aleksei Petrenko Mar 13 '16 at 20:21
11

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>
lepe
  • 24,677
  • 9
  • 99
  • 108
Woold
  • 783
  • 12
  • 23
  • 1
    Welcome to StackOverflow! Since this code may solve the problem, it's better if you add more explanation about how it works. – Tân Apr 09 '20 at 07:35
5

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.

lepe
  • 24,677
  • 9
  • 99
  • 108
dcode
  • 604
  • 7
  • 10
5

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>
mfluehr
  • 2,832
  • 2
  • 23
  • 31
jwerre
  • 9,179
  • 9
  • 60
  • 69
4

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.)

lepe
  • 24,677
  • 9
  • 99
  • 108
Ribo
  • 3,363
  • 1
  • 29
  • 35
4

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>
mrwolferinc
  • 143
  • 5
1

try to add this line , when you want to get rounded corners : ctx.lineCap = "round";

  • 1
    Hi, welcome to stack overflow. Have a look [here](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap). Are you sure this is a usable answer for rectangles? – Jeroen Heier Oct 05 '19 at 08:32
0

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();
    }