Cara menggambar Rectangle bulat pada Kanvas HTML?


Jawaban:


47

Kanvas HTML5 tidak menyediakan metode untuk menggambar persegi panjang dengan sudut bulat.

Bagaimana dengan menggunakan lineTo()dan arc()metode?

Anda juga dapat menggunakan quadraticCurveTo()metode alih-alih arc()metode.


Untuk beberapa alasan, saya sepertinya mengalami masalah dengan arcTo di Firefox 3.5 dan Opera 10.0. Mirip dengan situs ini: ditchnet.org/canvas/CanvasRoundedCornerExample.html
BGW

arcTo telah diperbaiki dalam versi terbaru FF.
Ash Blue

3
Bisakah Anda memberikan contoh?
Jean-Pierre Bécotte

324

Saya perlu melakukan hal yang sama dan menciptakan metode untuk melakukannya.

// 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
Jawaban sempurna ... Bagaimana ini belum asli kanvas ?! Terima kasih.
andygoestohollywood

1
kode memiliki bug, perlu melakukan stroke SETELAH mengisi, jika tidak, pada persegi panjang kecil mengisi akan menimpa stroke.
Zig Mandel

2
Saya tidak punya contoh di tangan tetapi saya harus memodifikasi urutan untuk kasus yang saya uji pada kode saya. Logikanya, bagaimana bisa stroke dengan benar (dengan smoothing menggunakan warna latar persegi) jika Anda belum mengisi kotak?
Zig Mandel

2
@Juan hei buruk saya, saya melihat posting blog dan menangkap berita gembira itu setelah. Saya bermaksud membatalkan pengeditan. Goodjob man memberi +1 pada Anda!
😁

6
Zig Mandel benar: itu harus diisi dan kemudian dibelai. Alasannya adalah bahwa jika Anda stroke dan kemudian mengisi maka lebar garis dibelah dua. Cobalah dengan lebar garis yang sangat tebal (katakanlah, 20) dan bandingkan persegi panjang bulat yang diisi dengan warna latar belakang dengan persegi panjang bulat yang tidak terisi. Lebar garis yang diisi akan menjadi setengah dari lebar garis yang tidak terisi.
Andrew Stacey

106

Saya mulai dengan solusi @ jhoff, tetapi menulis ulang untuk menggunakan parameter width / height, dan menggunakannya arcTomembuatnya sedikit lebih singkat:

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

Juga mengembalikan konteks sehingga Anda dapat sedikit rantai. Misalnya:

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

4
Saya tidak akan mengacaukan konteks rendering Canvas, kecuali untuk solusi yang baik itu.
Ash Blue

Masalah dengan solusi ini adalah Anda tidak dapat mengontrol jari-jari untuk setiap sudut secara mandiri. Tidak cukup fleksibel. Lihat solusi saya di bawah ini.
Corgalore

1
Ini adalah persegi panjang terpusat, jika seseorang membutuhkannya dengan sudut kiri atas (x,y), simpan konteksnya, tambahkan terjemahan (-w/2,-h/2), dan pulihkan konteksnya.
nessa.gp

Terima kasih, ini adalah satu-satunya yang telah bekerja untuk saya sejauh ini, yang lain memberi saya masalah ketika jari-jari lebih besar atau lebih besar daripada tinggi atau lebar. Diimplementasikan!
Howzieky

1
Perhatikan bahwa solusi ini berfungsi untuk membuat setiap poligon memiliki sudut membulat. Sebuah biola .
Doguleez

23

Juan, saya membuat sedikit peningkatan pada metode Anda untuk memungkinkan untuk mengubah masing-masing jari sudut sudut:

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

Gunakan seperti ini:

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
Pendekatan ini memberikan begitu banyak kendali atas sudut-sudut membulat. Mengapa ini bukan jawaban yang diterima>
Vighnesh Raut

@ VighneshRaut Mungkin karena jawaban ini menyalin / menempelkan jawaban asli yang diterima dan menambahkan sudut bulat. Saya memasukkannya ke dalam jawaban yang diterima memberi pujian untuk jawaban ini. Jawaban yang diterima memiliki contoh langsung dan sintaksinya lebih sederhana jika Anda ingin semua sudut dengan jari-jari yang sama (yang merupakan kasus paling umum). Terakhir, jawaban ini menyarankan untuk memodifikasi prototipe dari objek asli yang tidak boleh
Juan Mendes

12

The drawPolygonFungsi bawah ini dapat digunakan untuk menarik setiap poligon dengan sudut membulat.

Lihat itu berjalan di sini.

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

Fungsi menerima array dengan titik-titik poligon, seperti ini:

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

Ini adalah port dan versi yang lebih umum dari solusi yang diposting di sini .


9

Inilah yang saya tulis ... menggunakan busur bukan kurva kuadrat untuk kontrol yang lebih baik atas jari-jari. Juga, itu meninggalkan membelai dan memenuhi Anda

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

Berikut ini sebuah contoh:

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

Bagaimana ini memberi Anda kontrol yang lebih baik terhadap jari-jari? Saya pikir Anda akan memungkinkan untuk x / y jari-jari (sudut oval), dan juga menentukan jari-jari yang berbeda untuk setiap sudut
Juan Mendes

3
Anda r2dmungkin ingin dipanggil d2r.
Grumdrig

1
@JuanMendes: Bentuk (berbasis busur) dari sudut bundar dalam solusi ini lebih bundar daripada solusi (berbasis kuadrat) Anda. Saya pikir itulah yang dia maksud dengan "kontrol yang lebih baik atas radius".
Brent Bradburn

Saya juga menggunakan metode ini, lebih baik daripada menggunakan quadraticCurve. Tetapi jika Anda menggambar sesuatu yang lebih kompleks daripada persegi panjang itu SANGAT menyakitkan. Dengan ada metode otomatis seperti di kanvas 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();

inilah tepatnya yang saya cari
Daniel

Akhirnya jawaban singkat dan komprehensif yang benar-benar berfungsi. Terima kasih.
Franz Skuffka

5

Jadi ini didasarkan dari penggunaan lineJoin = "round" dan dengan proporsi yang tepat, matematika dan logika saya telah dapat membuat fungsi ini, ini tidak sempurna tetapi berharap itu membantu. Jika Anda ingin membuat setiap sudut memiliki radius yang berbeda, lihat di: https://p5js.org/reference/#/p5/rect

Ini dia:

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
Selamat datang di StackOverflow! Karena kode ini dapat menyelesaikan masalah, lebih baik jika Anda menambahkan lebih banyak penjelasan tentang cara kerjanya.
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();
    };
}

Karena Opera akan menggunakan WebKit, ini juga harus tetap valid dalam kasus lawas.


3

Untuk membuat fungsi lebih konsisten dengan cara normal menggunakan konteks kanvas, kelas konteks kanvas dapat diperluas untuk memasukkan metode ' fillRoundedRect' - yang dapat dipanggil dengan cara yang sama fillRectdisebut:

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
} 

Kode memeriksa untuk melihat apakah prototipe untuk konstruktor untuk objek konteks kanvas berisi properti ' fillRoundedRect' dan menambahkan satu - pertama kalinya. Itu dipanggil dengan cara yang sama seperti fillRectmetode:

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

Metode ini menggunakan arcTometode seperti yang dilakukan Grumdring. Dalam metode ini, thisadalah referensi ke ctxobjek. Argumen stroke secara default menjadi false jika tidak ditentukan. Argumen fill default untuk mengisi kotak jika tidak terdefinisi.

(Diuji pada Firefox, saya tidak tahu apakah semua implementasi mengizinkan ekstensi dengan cara ini.)


1
Saya sarankan menambahkan: rad = Math.min( rad, ww/2, hh/2 );agar ini bekerja dengan jari-jari besar seperti dalam versi @ Grumdrig.
Brent Bradburn

3

Berikut ini solusi menggunakan lineJoin untuk membulatkan sudut. Berfungsi jika Anda hanya membutuhkan bentuk yang padat tetapi tidak terlalu banyak jika Anda membutuhkan batas tipis yang lebih kecil dari radius batas.

    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

coba tambahkan baris ini, ketika Anda ingin mendapatkan sudut bulat: ctx.lineCap = "round";


1
Hai, selamat datang untuk menumpuk overflow. Silahkan lihat di sini . Apakah Anda yakin ini adalah jawaban yang dapat digunakan untuk persegi panjang?
Jeroen Heier
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.