Cara menggambar Rectangle bulat pada Kanvas HTML?

140

Saya menemukan bahwa hanya ada yang dapat mengisi persegi panjang, tetapi tidak ada sudut bulat, bagaimana saya bisa melakukannya?

DNB5brims
sumber
1
Orang ini membuat cara membuat persegi panjang bulat (diisi, dengan perbatasan ...) dalam metode yang bagus: js-bits.blogspot.com/2010/07/…
nerdess

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.

Coder-256
sumber
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>

Juan Mendes
sumber
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
Grumdrig
sumber
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);
Corgalore
sumber
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 .

moraes
sumber
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();
jhoff
sumber
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();
atom
sumber
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>

Woold
sumber
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.

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

Ribo
sumber
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"
    });
jwerre
sumber
0

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

Olexiy Marchenko
sumber
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