Wie zeichne ich ein abgerundetes Rechteck auf HTML-Leinwand?

140

Ich habe festgestellt, dass es nur Rechtecke geben kann, aber keine abgerundeten Ecken. Wie kann ich das tun?

DNB5brims
quelle
1
Dieser Typ hat in einer netten Methode erklärt
Nerdess

Antworten:

47

Die HTML5-Zeichenfläche bietet keine Methode zum Zeichnen eines Rechtecks ​​mit abgerundeten Ecken.

Wie wäre es mit den Methoden lineTo()und arc()?

Sie können die quadraticCurveTo()Methode auch anstelle der arc()Methode verwenden.

Coder-256
quelle
Aus irgendeinem Grund habe ich anscheinend Probleme mit arcTo in Firefox 3.5 und Opera 10.0. Ähnlich wie auf dieser Website: ditchnet.org/canvas/CanvasRoundedCornerExample.html
bgw
arcTo wurde in der neuesten Version von FF behoben.
Ash Blue
3
Können Sie ein Beispiel geben?
Jean-Pierre Bécotte
324

Ich musste das Gleiche tun und eine Methode dafür erstellen.

// 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
quelle
2
Perfekte Antwort ... Wie ist das noch nicht auf Leinwand?! Vielen Dank.
andygoestohollywood
1
Der Code hat einen Fehler. Er muss nach dem Füllen einen Strich ausführen. Andernfalls überschreibt die Füllung bei kleinen Rechtecken den Strich.
Zig Mandel
2
Ich habe das Beispiel nicht zur Hand, aber ich musste diese Reihenfolge für einen Fall ändern, den ich mit meinem Code getestet habe. Es ist logisch, wie kann es richtig streichen (mit Glättung unter Verwendung der rechten Hintergrundfarbe), wenn Sie das Rechteck noch nicht gefüllt haben?
Zig Mandel
2
@ Juan hey mein schlechtes, ich habe den Blog-Beitrag bemerkt und diesen Leckerbissen danach gefangen. Ich wollte die Bearbeitung rückgängig machen. Goodjob Mann + 1'd you! 😁
fabbb
6
Zig Mandel ist richtig: Es sollte gefüllt und dann gestreichelt werden. Der Grund ist, dass sich die Linienbreite halbiert, wenn Sie streichen und dann füllen. Versuchen Sie es mit einer wirklich dicken Linienbreite (z. B. 20) und vergleichen Sie ein abgerundetes Rechteck, das mit der Hintergrundfarbe gefüllt ist, mit einem abgerundeten Rechteck, das nicht gefüllt ist. Die Linienbreite des gefüllten ist die Hälfte der Linienbreite des nicht gefüllten.
Andrew Stacey
106

Ich habe mit der Lösung von @ jhoff angefangen, sie aber umgeschrieben, um die Parameter width / height zu verwenden, und die Verwendung arcTomacht sie etwas knapper:

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

Geben Sie auch den Kontext zurück, damit Sie ein wenig verketten können. Z.B:

ctx.roundRect(35, 10, 225, 110, 20).stroke(); //or .fill() for a filled rect
Grumdrig
quelle
4
Ich würde mich nicht mit dem Canvas-Rendering-Kontext anlegen, außer mit dieser guten Lösung.
Ash Blue
Das Problem bei dieser Lösung ist, dass Sie den Radius nicht für jede Ecke unabhängig steuern können. Nicht flexibel genug. Siehe meine Lösung unten.
Corgalore
1
Dies ist ein zentriertes Rechteck. Wenn jemand eines mit der oberen linken Ecke benötigt (x,y), speichern Sie den Kontext, fügen Sie eine Übersetzung hinzu (-w/2,-h/2)und stellen Sie den Kontext wieder her.
nessa.gp
Vielen Dank, dies ist die einzige, die bisher für mich gearbeitet hat, die anderen geben mir Probleme, wenn der Radius größer oder größer als die Höhe oder Breite war. Implementiert!
Howzieky
1
Beachten Sie, dass mit dieser Lösung jedes Polygon abgerundete Ecken aufweist. Eine Geige .
Doguleez
23

Juan, ich habe Ihre Methode leicht verbessert, damit jeder rechteckige Eckenradius einzeln geändert werden kann:

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

Verwenden Sie es so:

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
quelle
1
Dieser Ansatz bietet so viel Kontrolle über die abgerundeten Ecken. Warum ist dies nicht die akzeptierte Antwort
?
@VighneshRaut Wahrscheinlich, weil diese Antwort die ursprünglich akzeptierte Antwort kopiert / eingefügt und abgerundete Ecken hinzugefügt hat. Ich habe es in die akzeptierte Antwort aufgenommen und diese Antwort anerkannt. Die akzeptierte Antwort enthält ein Live-Beispiel und die Syntax ist einfacher, wenn Sie alle Ecken mit demselben Radius haben möchten (was der häufigste Fall ist). Schließlich schlägt diese Antwort vor, den Prototyp eines nativen Objekts zu ändern, das ein Nein-Nein ist
Juan Mendes
12

Mit der folgenden drawPolygonFunktion können Sie jedes Polygon mit abgerundeten Ecken zeichnen .

Sehen Sie es hier laufen.

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

Die Funktion empfängt ein Array mit den Polygonpunkten wie folgt:

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

Dies ist ein Port und eine generische Version einer Lösung gepostet hier .

Moraes
quelle
9

Hier ist eine, die ich geschrieben habe ... verwendet Bögen anstelle von quadratischen Kurven, um den Radius besser kontrollieren zu können. Außerdem überlässt es Ihnen das Streicheln und Füllen

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

Hier ist ein Beispiel:

var _e = document.getElementById('#my_canvas');
var _cxt = _e.getContext("2d");
_cxt.roundRect(35,10,260,120,20);
_cxt.strokeStyle = "#000";
_cxt.stroke();
jhoff
quelle
Wie können Sie den Radius besser kontrollieren? Ich dachte, Sie würden x / y-Radien (ovale Ecken)
Juan Mendes
3
Sie möchten r2dwahrscheinlich angerufen werden d2r.
Grumdrig
1
@JuanMendes: Die (bogenbasierten) Formen der abgerundeten Ecken in dieser Lösung sind kreisförmiger als die Ihrer (quadratischen) Lösung. Ich denke, das meinte er mit "besserer Kontrolle über den Radius".
Brent Bradburn
Ich habe auch diese Methode verwendet, es ist besser als quadraticCurve. Aber wenn Sie etwas Komplexeres als ein Rechteck zeichnen, ist es WIRKLICH schmerzhaft. Mit gab es eine automatische Methode wie in Android Canvas.
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
quelle
das war genau das, wonach ich gesucht habe
Daniel
Endlich eine kurze und umfassende Antwort, die tatsächlich funktioniert. Vielen Dank.
Franz Skuffka
5

Das basiert also auf der Verwendung von lineJoin = "round" und mit den richtigen Proportionen, Mathematik und Logik konnte ich diese Funktion erstellen. Dies ist nicht perfekt, aber ich hoffe, es hilft. Wenn Sie möchten, dass jede Ecke einen anderen Radius hat, schauen Sie unter: https://p5js.org/reference/#/p5/rect

Hier gehts:

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
quelle
1
Willkommen bei StackOverflow! Da dieser Code das Problem möglicherweise löst, ist es besser, wenn Sie weitere Erklärungen zur Funktionsweise hinzufügen.
Tân
3

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

Da Opera WebKit verwendet, sollte dies auch im Legacy-Fall gültig bleiben.

dcode
quelle
3

Um die Funktion mit den normalen fillRoundedRectMethoden zur Verwendung eines Canvas-Kontexts konsistenter zu machen, kann die Canvas-Kontextklasse um eine ' ' -Methode erweitert werden, die auf die gleiche Weise fillRectaufgerufen werden kann:

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
} 

Der Code prüft, ob der Prototyp für den Konstruktor für das Canvas-Kontextobjekt eine ' fillRoundedRect' - Eigenschaft enthält , und fügt beim ersten Mal eine hinzu. Es wird auf die gleiche Weise wie die fillRectMethode aufgerufen :

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

Die Methode verwendet die arcToMethode wie Grumdring. In der Methode thisist ein Verweis auf diectx Objekt. Das Strichargument ist standardmäßig false, wenn es nicht definiert ist. Das Füllargument füllt standardmäßig das Rechteck, wenn es nicht definiert ist.

(Unter Firefox getestet, weiß ich nicht, ob alle Implementierungen eine Erweiterung auf diese Weise zulassen.)

Ribo
quelle
1
Ich schlage vor, rad = Math.min( rad, ww/2, hh/2 );Folgendes hinzuzufügen: damit dies mit großen Radien funktioniert, wie in der Version von @ Grumdrig.
Brent Bradburn
3

Hier ist eine Lösung mit einem lineJoin , um die Ecken abzurunden . Funktioniert, wenn Sie nur eine feste Form benötigen, aber nicht so sehr, wenn Sie einen dünnen Rand benötigen, der kleiner als der Randradius ist.

    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
quelle
0

Versuchen Sie, diese Linie hinzuzufügen, wenn Sie abgerundete Ecken erhalten möchten: ctx.lineCap = "round";

Olexiy Marchenko
quelle
1
Hallo, willkommen zum Stapelüberlauf. Schauen Sie hier . Sind Sie sicher, dass dies eine brauchbare Antwort für Rechtecke ist?
Jeroen Heier