Cómo dibujar un rectángulo redondeado en HTML Canvas?

Descubrí que solo puede rellenar un rectángulo, pero no es un esquinero redondeado, ¿cómo puedo hacer eso?

El canvas HTML5 no proporciona un método para dibujar un rectángulo con esquinas redondeadas.

¿Qué hay de usar los métodos lineTo() y arc() ?

También puede usar el método quadraticCurveTo() lugar del método arc() .

Necesitaba hacer lo mismo y crear un método para hacerlo.

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

Empecé con la solución de @ jhoff, pero la reescribí para usar los parámetros de ancho / alto, y usar arcTo hace un poco más concisa:

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

También devolviendo el contexto para que pueda encadenar un poco. P.ej:

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

Juan, hice una ligera mejora en tu método para permitir cambiar el radio de cada esquina del rectángulo de forma individual:

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

Úselo así:

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

La función drawPolygon continuación se puede utilizar para dibujar cualquier polígono con esquinas redondeadas.

Véalo corriendo aquí.

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

La función recibe una matriz con los puntos del polígono, como esta:

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

Este es un puerto y una versión más genérica de una solución publicada aquí .

Aquí hay uno que escribí … usa arcos en lugar de curvas cuadráticas para un mejor control sobre el radio. Además, deja la caricia y el relleno a usted

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

Aquí hay un ejemplo:

 var _e = document.getElementById('#my_canvas'); var _cxt = _e.getContext("2d"); _cxt.roundRect(35,10,260,120,20); _cxt.strokeStyle = "#000"; _cxt.stroke(); 
  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(); 

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+hr); this.arcTo(x, y+hr, x+r, y+h, r); this.lineTo(x+wr, y+h); this.arcTo(x+wr, y+h, x+w, y+hr, r); this.lineTo(x+w, y+r); this.arcTo(x+w, y+r, x+wr, 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(); }; } 

Como Opera se está ejecutando WebKit, esto también debería seguir siendo válido en el caso heredado.

Para hacer que la función sea más consistente con los medios normales de usar un contexto de canvas, la clase de contexto canvas se puede extender para incluir un método ‘ fillRoundedRect ‘, que se puede llamar de la misma manera que se llama a fillRect :

 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 } 

El código verifica si el prototipo para el constructor del objeto de contexto canvas contiene una propiedad ‘ fillRoundedRect ‘ y agrega una – la primera vez. Se invoca de la misma manera que el método fillRect :

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

El método usa el método arcTo como lo hizo Grumdring. En el método, this es una referencia al objeto ctx . El argumento de trazo se predetermina a falso si no está definido. El argumento de relleno está predeterminado para llenar el rectángulo si no está definido.

(Probado en Firefox, no sé si todas las implementaciones permiten la extensión de esta manera).