Làm cách nào để vẽ một hình chữ nhật tròn trên HTML Canvas?


Câu trả lời:


47

Canvas HTML5 không cung cấp phương pháp để vẽ hình chữ nhật với các góc tròn.

Làm thế nào về việc sử dụng lineTo()arc()phương pháp?

Bạn cũng có thể sử dụng quadraticCurveTo()phương thức thay vì arc()phương thức.


Vì một số lý do, tôi dường như gặp vấn đề với arcTo trong Firefox 3.5 và Opera 10.0. Tương tự như trang web này: ditchnet.org/canvas/CanvasRoundedCornerExample.html
bgw

arcTo đã được sửa trong phiên bản FF mới nhất.
Ash Blue

3
bạn có thể cung cấp một ví dụ?
Jean-Pierre Bécotte

324

Tôi cần phải làm điều tương tự và tạo ra một phương pháp để làm điều đó.

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

}
<canvas id="rounded-rect" width="500" height="200">
  <!-- Insert fallback content here -->
</canvas>


2
Câu trả lời hoàn hảo ... Làm thế nào điều này không có nguồn gốc từ vải? Cảm ơn.
andygoestohollywood

1
mã có một lỗi, nó cần phải thực hiện đột quỵ SAU, nếu không, trên các hình chữ nhật nhỏ, phần điền sẽ ghi đè lên nét.
Zig Mandel

2
Tôi không có ví dụ trong tay nhưng tôi phải sửa đổi thứ tự đó cho trường hợp tôi đã kiểm tra mã của mình. Nó hợp lý, làm thế nào nó có thể đột quỵ chính xác (với làm mịn bằng cách sử dụng màu nền trực tràng) nếu bạn chưa lấp đầy trực tràng?
Zig Mandel

2
@Juan hey xấu của tôi, tôi nhận thấy bài đăng trên blog và bắt gặp mẩu tin đó sau. Tôi có nghĩa là hoàn tác chỉnh sửa. Người đàn ông tốt bụng + 1'd bạn! 😁
fabbb

6
Zig Mandel là chính xác: nó cần được lấp đầy và sau đó vuốt ve. Lý do là nếu bạn vuốt và sau đó điền thì chiều rộng của dòng được giảm một nửa. Hãy thử nó với chiều rộng đường thực sự dày (giả sử, 20) và so sánh một hình chữ nhật tròn được tô màu nền với hình chữ nhật tròn không được lấp đầy. Độ rộng của dòng được điền sẽ bằng một nửa chiều rộng của dòng không được điền.
Andrew Stacey

106

Tôi đã bắt đầu với giải pháp của @ jhoff, nhưng viết lại nó để sử dụng các tham số chiều rộng / chiều cao và sử dụng arcTolàm cho nó trở nên ngắn gọn hơn một chút:

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

Cũng trả lại bối cảnh để bạn có thể xâu chuỗi một chút. Ví dụ:

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

4
Tôi sẽ không gây rối với bối cảnh kết xuất Canvas, ngoại trừ giải pháp tốt đó.
Ash Blue

Vấn đề với giải pháp này là bạn không thể kiểm soát bán kính cho từng góc một cách độc lập. Không đủ linh hoạt. Xem giải pháp của tôi dưới đây.
Corginois

1
Đây là một hình chữ nhật ở giữa, nếu ai đó cần một góc với góc trên bên trái của nó (x,y), lưu bối cảnh, thêm bản dịch vào (-w/2,-h/2)và khôi phục bối cảnh.
nessa.gp

Cảm ơn bạn, đây là người duy nhất đã làm việc cho tôi cho đến nay, những người khác cho tôi các vấn đề khi bán kính lớn hơn hoặc lớn hơn chiều cao hoặc chiều rộng. Thực hiện!
Howzieky

1
Lưu ý rằng giải pháp này hoạt động để làm cho bất kỳ đa giác có góc tròn. Một câu đố .
Doguleez

23

Juan, tôi đã cải thiện một chút phương pháp của bạn để cho phép thay đổi từng bán kính góc hình chữ nhật:

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

Sử dụng nó như thế này:

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

1
Cách tiếp cận này cung cấp rất nhiều kiểm soát trên các góc tròn. Tại sao đây không phải là câu trả lời được chấp nhận>
Vighnesh Raut

@VighneshRaut Có lẽ vì câu trả lời này sao chép / dán câu trả lời được chấp nhận ban đầu và thêm các góc tròn. Tôi kết hợp nó vào câu trả lời được chấp nhận đã cho tín dụng cho câu trả lời này. Câu trả lời được chấp nhận có một ví dụ trực tiếp và cú pháp đơn giản hơn nếu bạn muốn tất cả các góc có cùng bán kính (đó là trường hợp phổ biến nhất). Cuối cùng, câu trả lời này đề nghị sửa đổi nguyên mẫu của một vật thể bản địa không có
Juan Mendes

12

Các drawPolygonchức năng dưới đây có thể được sử dụng để vẽ bất kỳ đa giác với các góc tròn.

Xem nó chạy ở đây.

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

Hàm nhận được một mảng với các điểm đa giác, như thế này:

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

Đây là một cổng và một phiên bản chung hơn của một giải pháp được đăng ở đây .


9

Đây là một cái tôi đã viết ... sử dụng các cung thay vì các đường cong bậc hai để kiểm soát bán kính tốt hơn. Ngoài ra, nó để lại vuốt ve và làm đầy cho bạn

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

Đây là một ví dụ:

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

Làm thế nào điều này cung cấp cho bạn kiểm soát tốt hơn bán kính? Tôi nghĩ rằng bạn sẽ cho phép bán kính x / y (góc hình bầu dục) và cũng chỉ định bán kính khác nhau cho mỗi góc
Juan Mendes

3
r2dCó lẽ bạn muốn được gọi d2r.
Grumdrig

1
@JuanMendes: Hình dạng (dựa trên vòng cung) của các góc tròn trong giải pháp này có hình tròn hơn so với hình dạng của giải pháp (dựa trên bậc hai) của bạn. Tôi nghĩ đó là ý của anh ấy khi "kiểm soát tốt hơn bán kính".
Brent Bradburn

Tôi cũng đã sử dụng phương pháp này, nó tốt hơn so với sử dụng quadraticCurve. Nhưng nếu bạn vẽ một cái gì đó phức tạp hơn hình chữ nhật thì thật sự đau đớn. Với một phương pháp tự động như trong canvas của Android.
Aleksei Petrenko

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

đây chính xác là thứ tôi đang tìm kiếm
Daniel

Cuối cùng một câu trả lời ngắn gọn và toàn diện mà thực sự hoạt động. cảm ơn.
Franz Skuffka

5

Vì vậy, điều này dựa trên việc sử dụng lineJoin = "round" và với tỷ lệ thích hợp, toán học và logic tôi đã có thể thực hiện chức năng này, điều này không hoàn hảo nhưng hy vọng nó sẽ giúp ích. Nếu bạn muốn làm cho mỗi góc có bán kính khác nhau, hãy xem: https://p5js.org/reference/#/p5/rect

Ở đây ya đi:

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>


1
Chào mừng bạn đến với StackOverflow! Vì mã này có thể giải quyết vấn đề, sẽ tốt hơn nếu bạn thêm giải thích về cách thức hoạt động của nó.
Tân

3

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

Vì Opera đang sử dụng WebKit, điều này cũng sẽ vẫn còn hiệu lực trong trường hợp kế thừa.


3

Để làm cho hàm phù hợp hơn với các phương tiện thông thường sử dụng bối cảnh canvas, lớp bối cảnh canvas có thể được mở rộng để bao gồm một fillRoundedRectphương thức '' - có thể được gọi theo cùng một cách fillRectđược gọi:

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
} 

Mã kiểm tra xem liệu nguyên mẫu cho hàm tạo cho đối tượng bối cảnh canvas có chứa thuộc tính ' fillRoundedRect' hay không và thêm một - lần đầu tiên. Nó được gọi theo cách tương tự như fillRectphương thức:

  ctx.fillStyle = "#eef";  ctx.strokeStyle = "#ddf";
  // ctx.fillRect(10,10, 200,100);
  ctx.fillRoundedRect(10,10, 200,100, 5);

Phương thức sử dụng arcTophương thức như Grumdring đã làm. Trong phương thức, thislà một tham chiếu đến ctxđối tượng. Đối số đột quỵ mặc định là false nếu không xác định. Đối số điền mặc định để điền vào hình chữ nhật nếu không xác định.

(Đã thử nghiệm trên Firefox, tôi không biết liệu tất cả các triển khai có cho phép mở rộng theo cách này không.)


1
Tôi đề nghị thêm: rad = Math.min( rad, ww/2, hh/2 );để nó hoạt động với bán kính lớn như trong phiên bản của @ Grumdrig.
Brent Bradburn

3

Đây là một giải pháp sử dụng lineJoin để làm tròn các góc. Hoạt động nếu bạn chỉ cần một hình dạng rắn nhưng không quá nhiều nếu bạn cần một đường viền mỏng nhỏ hơn bán kính đường viền.

    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];
    let ctx = canvas.getContext('2d');

    roundedRect(ctx, {
        x: 10,
        y: 10,
        width: 200,
        height: 100,
        radius: 10,
        color: "red"
    });

0

hãy thử thêm dòng này, khi bạn muốn có được các góc tròn: ctx.lineCap = "round";


1
Xin chào, chào mừng bạn đến với chồng tràn. Có một cái nhìn ở đây . Bạn có chắc chắn đây là một câu trả lời có thể sử dụng cho hình chữ nhật?
Jeroen Heier
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.