HTML Canvas'a yuvarlatılmış bir Dikdörtgen nasıl çizilir?


Yanıtlar:


47

HTML5 tuvali, köşeleri yuvarlatılmış dikdörtgen çizme yöntemi sunmaz.

lineTo()Ve arc()yöntemlerini kullanmaya ne dersiniz ?

quadraticCurveTo()Yöntem yerine yöntemi de kullanabilirsiniz arc().


Nedense, FireTo 3.5 ve Opera 10.0'da arcTo ile ilgili sorunlar yaşıyorum gibi görünüyor. Bu siteye benzer: ditchnet.org/canvas/CanvasRoundedCornerExample.html
bgw

arcTo, FF'nin en son sürümünde düzeltildi.
Ash Blue

3
Bir örnek verebilir misiniz?
Jean-Pierre Bécotte

324

Aynı şeyi yapmam gerekiyordu ve bunu yapmak için bir yöntem oluşturdum.

// 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
Mükemmel cevap ... Bu henüz tuval için nasıl doğal değil ?! Teşekkürler.
andygoestohollywood

1
kodda bir hata var, dolgu SONRASI kontur yapmalı, aksi takdirde küçük dikdörtgenlerde dolgu konturun üzerine yazacaktır.
Zig Mandel

2
Elimdeki örneği yok ama kodumu test ettiğim bir dava için bu siparişi değiştirmek zorunda kaldım. Mantıksal, rektifi henüz doldurmadıysanız nasıl doğru şekilde vuruş yapabilir (rect arka plan rengini kullanarak yumuşatma ile)?
Zig Mandel

2
@Juan hey benim hatam, blog gönderisini fark ettim ve sonra bu çöplüğü yakaladım. Düzenlemeyi geri almak istedim. Goodjob adam + 1'ledi! 😁
fabbb

6
Zig Mandel doğrudur: doldurulmalı ve ardından okşulmalıdır. Bunun nedeni, kontur ve sonra doldurursanız çizgi genişliğinin yarıya inmesidir. Bunu gerçekten kalın bir çizgi genişliği (örneğin, 20) ile deneyin ve arka plan rengiyle doldurulmuş yuvarlak bir dikdörtgeni doldurulmamış yuvarlak bir dikdörtgeyle karşılaştırın. Doldurulmuş olanın çizgi genişliği doldurulmamış olanın genişliğinin yarısı olacaktır.
Andrew Stacey

106

@ Jhoff'un çözümü ile başladım, ancak genişlik / yükseklik parametrelerini kullanmak için yeniden yazdım ve kullanmak arcTobiraz daha keskin hale getirdi:

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

Ayrıca bağlam döndürerek biraz zincirleyebilirsiniz. Örneğin:

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

4
Tuval oluşturma bağlamıyla uğraşmazdım, bu iyi çözüm dışında.
Ash Blue

Bu çözümün sorunu, her köşe için yarıçapı bağımsız olarak kontrol edememenizdir. Yeterince esnek değil. Aşağıdaki çözümüme bakın.
Ağustos'ta Corgalore

1
Bu ortalanmış bir dikdörtgendir, eğer birisi sol üst köşesinde bir tane gerekiyorsa (x,y), içeriği kaydedin, bir çeviri ekleyin (-w/2,-h/2)ve içeriği geri yükleyin.
nessa.gp

Teşekkür ederim, bu benim için şimdiye kadar işe yarayan tek kişi, diğerleri yarıçap yükseklik veya genişlikten daha büyük veya daha büyük olduğunda sorunları veriyor. Uygulanan!
Howzieky

1
Bu çözümün herhangi bir çokgenin yuvarlatılmış köşeleri olmasını sağlamak için çalıştığını unutmayın. Bir keman .
Doguleez

23

Juan, her dikdörtgen köşe yarıçapını ayrı ayrı değiştirmenize izin vermek için yönteminizde küçük bir iyileştirme yaptım:

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

Şöyle kullanın:

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
Bu yaklaşım yuvarlatılmış köşeler üzerinde çok fazla kontrol sağlar. Bu neden kabul edilen cevap değil>
Vighnesh Raut

@VighneshRaut Muhtemelen bu cevap kabul edilen orijinal cevabı kopyalayıp yapıştırdığı ve yuvarlatılmış köşeler eklediği için. Kabul ettiğim cevaba dahil ettim, bu cevaba kredi verdi. Kabul edilen cevabın canlı bir örneği vardır ve aynı yarıçapa sahip tüm köşeleri (en yaygın durumdur) istiyorsanız sözdizimi daha basittir. Son olarak, bu cevap, hayır olan yerel bir nesnenin prototipini değiştirmeyi öneriyor
Juan Mendes

12

Aşağıdaki drawPolygonfonksiyon, köşeleri yuvarlatılmış herhangi bir çokgen çizmek için kullanılabilir .

Burada çalıştığını görün.

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

İşlev, çokgen noktaları olan bir dizi alır, örneğin:

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

Bu bir bağlantı noktası ve burada yayınlanan bir çözümün daha genel bir sürümüdür .


9

İşte yazdığım biri ... yarıçap üzerinde daha iyi kontrol için ikinci dereceden eğriler yerine yay kullanıyor. Ayrıca, okşayarak ve size dolduruyor

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

İşte bir örnek:

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

Bu size yarıçap üzerinde nasıl daha iyi kontrol sağlar? X / y yarıçaplarına (oval köşeler) izin vereceğinizi ve her köşe için farklı yarıçaplar belirleyeceğinizi sanıyordum
Juan Mendes

3
Sizin r2dmuhtemelen dememizi istiyor d2r.
Grumdrig

1
@JuanMendes: Bu çözümdeki yuvarlak köşelerin (ark tabanlı) şekilleri, (ikinci dereceden tabanlı) çözümünüzden daha daireseldir. "Yarıçap üzerinde daha iyi kontrol" ile kastettiğini düşünüyorum.
Brent Bradburn

Ayrıca bu yöntemi kullandım, quadraticCurve kullanmaktan daha iyidir. Ancak, dikdörtgenden daha karmaşık bir şey çizerseniz, gerçekten acı vericidir. Android tuvalinde olduğu gibi otomatik bir yöntem vardı.
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();

tam da aradığım şey buydu
Daniel

Sonunda gerçekten işe yarayan kısa ve kapsamlı bir cevap. Teşekkürler.
Franz Skuffka

5

Yani bu lineJoin = "round" kullanarak dayanmaktadır ve uygun oranlarda, matematik ve mantık ile bu işlevi yapmak mümkün, bu mükemmel değil ama yardımcı olur umarım. Her köşenin farklı bir yarıçapa sahip olmasını istiyorsanız şu adrese göz atın: https://p5js.org/reference/#/p5/rect

İşte başlıyoruz:

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
StackOverflow'a hoş geldiniz! Bu kod sorunu çözebileceğinden, nasıl çalıştığı hakkında daha fazla açıklama eklemek daha iyidir.
Tan

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

Opera WebKit'e gittiğinden, bu eski durumda da geçerli kalmalıdır.


3

Fonksiyonu bir tuval bağlamı kullanmanın normal yöntemleriyle daha tutarlı hale getirmek için, tuval bağlam sınıfı bir ' fillRoundedRect' yöntemi içerecek şekilde genişletilebilir - aynı şekilde fillRectçağrılabilir:

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
} 

Kod, canvas bağlam nesnesinin yapıcısının prototipinin bir ' fillRoundedRect' özelliği içerip içermediğini ve ilk kez bir tane ekleyip eklemediğini kontrol eder. fillRectYöntemle aynı şekilde çağrılır :

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

arcToYöntem, Grumdring'in yaptığı gibi yöntemi kullanır . Yöntemde, nesneye thisbir referanstır ctx. Undefined ise kontur bağımsız değişkeni varsayılan olarak false olur. Dolgu bağımsız değişkeni, tanımsızsa dikdörtgeni doldurur.

(Firefox'ta test edildi, tüm uygulamaların bu şekilde uzantıya izin verip vermediğini bilmiyorum.)


1
Ben eklemenizi öneririz: rad = Math.min( rad, ww/2, hh/2 );böylece bu @ Grumdrig'in sürümünde olduğu gibi büyük yarıçaplarla çalışır.
Brent Bradburn

3

İşte köşeleri yuvarlamak için bir lineJoin kullanarak bir çözüm . Sadece düz bir şekle ihtiyacınız varsa çalışır, ancak sınır yarıçapından daha küçük ince bir kenarlığa ihtiyacınız varsa çok fazla değil.

    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

yuvarlatılmış köşeler elde etmek istediğinizde bu satırı eklemeyi deneyin: ctx.lineCap = "round";


1
Merhaba, taşma yığını için hoş geldiniz. Buraya bir bak . Bunun dikdörtgenler için kullanılabilir bir cevap olduğundan emin misiniz?
Jeroen Heier
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.