2009-08-10 75 views

Respuesta

39

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

¿Qué le parece usar los métodos lineTo() y arc()?

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

+0

Por alguna razón, parece que tengo problemas con arcTo en Firefox 3.5 y Opera 10.0. Similar a este sitio: http://ditchnet.org/canvas/CanvasRoundedCornerExample.html – bgw

+0

arcTo se ha corregido en la última versión de FF. –

231

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

 
}
<canvas id="rounded-rect" width="500" height="200"> 
 
    <!-- Insert fallback content here --> 
 
</canvas>

+11

Funciona perfectamente. Esta debería ser la respuesta seleccionada. +1 –

+2

Guau, acabo de recibir el primer voto negativo de esta respuesta, sería bueno que el que menospreció explicara por qué –

+1

Respuesta perfecta ... ¿Cómo es que todavía no es nativo del lienzo? Gracias. – andygoestohollywood

6

Aquí hay una que escribí ... usa arcos en vez de curvas cuadráticas para un mejor control sobre el radio Además, deja la caricia y llenando hasta que

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

He aquí 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(); 
+0

¿Cómo te da esto un mejor control sobre el radio? Pensé que ibas a permitir x/y radios (esquinas ovales), y también especificando diferentes radios para cada esquina –

+3

Tu 'r2d' probablemente se llame' d2r'. – Grumdrig

+1

@JuanMendes: Las formas (a base de arco) de las esquinas redondeadas en esta solución son más circulares que las de su solución (cuadrática). Creo que eso es lo que quiso decir con "mejor control sobre el radio". – nobar

11

Juan, hice una ligera mejora a su método para que puedan cambiar cada radio de la 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(); 
    } 
} 

utilizar de esta manera:

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

empecé con solución de @ jhoff, pero volvió a escribir para utilizar parámetros de anchura/altura, y el uso de arcTo hace que sea 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 regresa el contexto así se pueden encadenar un poco. Por ejemplo:

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

No me meteré con el contexto de representación de lienzo, a excepción de esa buena solución. –

+0

El problema con esta solución es que no puede controlar el radio para cada esquina de forma independiente. No lo suficientemente flexible. Vea mi solución a continuación. – Corgalore

+1

+1 para una solución elegante muy agradable. – TachyonVortex

2

Para hacer la función más consistente con los medios normales de utilización de un contexto lienzo, la clase de contexto de la lona se puede ampliar para incluir una 'fillRoundedRect' método - que se puede llamar de la misma manera fillRect se llama:

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 comprueba si el prototipo del constructor para el objeto de contexto contiene un lienzo 'fillRoundedRect' propiedad y añade uno - la primera vez. Se invoca de la misma manera como 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 utiliza 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.)

+1

Sugiero agregar: 'rad = Math.min (rad, ww/2, hh/2);' para que funcione con radios grandes como en la versión de @ Grumdrig. – nobar

2

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

Dado que Opera se va a WebKit, esto también debería seguir siendo válido en el caso heredado.

7

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

See it running here.

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 este:

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érico de una solución publicada here.

2
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(); 
Cuestiones relacionadas