How to draw a rounded rectangle using HTML Canvas?

Viewed 200108

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?

15 Answers

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.

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>

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>

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