如何在HTML Canvas上绘制圆角矩形?

DNB*_*ims 130 html5 canvas

我发现只有填充矩形,但没有圆角,我怎么能这样做?

Jua*_*des 305

我需要做同样的事情并创建一个方法来做到这一点.

// 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);

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

}
Run Code Online (Sandbox Code Playgroud)
<canvas id="rounded-rect" width="500" height="200">
  <!-- Insert fallback content here -->
</canvas>
Run Code Online (Sandbox Code Playgroud)

  • Zig Mandel是正确的:它应该被填充然后抚摸.原因是如果你划动然后填充然后线宽减半.尝试使用非常粗的线宽(例如20)并将填充了背景颜色的圆角矩形与未填充的圆角矩形进行比较.填充的线宽将是未填充线宽的一半. (6认同)
  • 完美的答案...这还不是画布上的本机吗?谢谢。 (2认同)
  • 该代码有一个错误,它需要在填充之后进行描边,否则在小矩形上填充将覆盖描边。 (2认同)
  • 我手边没有示例,但我必须修改我在代码上测试的案例的顺序.它的逻辑,如果你还没有填充矩形,它如何正确地描边(使用rect背景颜色平滑)? (2认同)
  • @Juan嘿,我的坏,我注意到博客文章,并抓住了那个花絮后.我打算撤消编辑.Goodjob男人给你+1了! (2认同)
  • **Firefox 还不支持**(尚):https://caniuse.com/mdn-api_canvasrenderingcontext2d_roundrect 或 https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/roundRect#browser_compatibility (2认同)

Gru*_*rig 98

我从@jhoff的解决方案开始,但重新编写它以使用宽度/高度参数,并使用arcTo使它更简洁:

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;
}
Run Code Online (Sandbox Code Playgroud)

还返回上下文,以便您可以链接一点.例如:

ctx.roundRect(35, 10, 225, 110, 20).stroke(); //or .fill() for a filled rect
Run Code Online (Sandbox Code Playgroud)

  • 除了那个好的解决方案,我不会搞乱Canvas渲染上下文. (4认同)

小智 45

HTML5画布不提供绘制带圆角的矩形的方法.

使用lineTo()arc()方法怎么样?

您也可以使用该quadraticCurveTo()方法而不是arc()方法.

  • 你能举个例子吗? (2认同)

Kai*_*ido 27

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,
  • 或四个花车,每个角一个,
  • 或者相同的组合,但使用DOMPointInit对象(具有浮动xy属性的任何对象),表示每个角的 x 半径和 y 半径,并允许使用椭圆弧而不是“仅”圆弧。

现在每个主要浏览器都支持它,对于较旧的浏览器,您可以找到我在这个 repo中制作的一个polyfill 。

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();
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdn.jsdelivr.net/gh/Kaiido/roundRect@main/roundRect.js"></script>
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)


Cor*_*ore 19

Juan,我对你的方法做了一些改进,允许分别改变每个矩形角半径:

/** 
 * 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();
    }
} 
Run Code Online (Sandbox Code Playgroud)

像这样使用它:

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);
Run Code Online (Sandbox Code Playgroud)

  • 这种方法可以很好地控制圆角。为什么这不是公认的答案&gt; (2认同)
  • @VighneshRaut 可能是因为这个答案复制/粘贴了原始接受的答案并添加了圆角。我将其纳入已接受的答案中,给予了这个答案以信用。接受的答案有一个实例,如果您确实希望所有角具有相同的半径(这是最常见的情况),则语法会更简单。最后,这个答案建议修改本机对象的原型,这是一个禁忌 (2认同)

mor*_*aes 11

drawPolygon下面的函数可用于绘制任何带圆角的多边形.

看到它在这里运行.

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))];
};
Run Code Online (Sandbox Code Playgroud)

该函数接收带有多边形点的数组,如下所示:

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();
Run Code Online (Sandbox Code Playgroud)

这是一个端口和此处发布的解决方案的更通用版本.


jho*_*off 8

这是我写的一个...使用弧而不是二次曲线来更好地控制半径.此外,它留下了抚摸和填补你

    /* Canvas 2d context - roundRect
 *
 * Accepts 5 parameters, the start_x and start_y points, the end_x and end_y points, and 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();
}
Run Code Online (Sandbox Code Playgroud)

这是一个例子:

var _e = document.getElementById('#my_canvas');
var _cxt = _e.getContext("2d");
_cxt.roundRect(35,10,260,120,20);
_cxt.strokeStyle = "#000";
_cxt.stroke();
Run Code Online (Sandbox Code Playgroud)

  • 您的`r2d`可能想要被称为`d2r`. (3认同)
  • @JuanMendes:此解决方案中圆角的(基于圆弧的)形状比(基于二次的)解决方案的圆角形状更圆。我认为这就是他所说的“更好地控制半径”的意思。 (2认同)

Woo*_*old 8

所以这是基于使用 lineJoin="round" 并且通过适当的比例、数学和逻辑我已经能够创建这个函数,这并不完美,但希望它有所帮助。如果你想让每个角有不同的半径,看看:https : //p5js.org/reference/#/p5/rect

给你:

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();
}
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)
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);
Run Code Online (Sandbox Code Playgroud)


ato*_*tom 7

    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();
Run Code Online (Sandbox Code Playgroud)


dco*_*ode 5

歌剧,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();
    };
}
Run Code Online (Sandbox Code Playgroud)

由于 Opera 正在使用 WebKit,因此这在遗留情况下也应该保持有效。


jwe*_*rre 5

lineJoin这是使用该属性来圆角的解决方案。如果您只需要一个实体形状,那么它可以工作,但如果您需要一个小于边框半径的薄边框,则效果就不那么好了。

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"
});
Run Code Online (Sandbox Code Playgroud)
<canvas></canvas>
Run Code Online (Sandbox Code Playgroud)