HTML 캔버스에 둥근 사각형을 그리는 방법?


답변:


47

HTML5 캔버스는 모서리가 둥근 사각형을 그리는 방법을 제공하지 않습니다.

lineTo()arc()방법을 사용하는 것은 어떻습니까?

quadraticCurveTo()메소드 대신 메소드를 사용할 수도 있습니다 arc().


어떤 이유로 Firefox 3.5 및 Opera 10.0에서 arcTo에 문제가있는 것 같습니다. 이 사이트와 유사합니다 : ditchnet.org/canvas/CanvasRoundedCornerExample.html
bgw 2009

arcTo는 최신 버전의 FF에서 수정되었습니다.
Ash Blue

3
예를 들어 줄 수 있습니까?
Jean-Pierre Bécotte

324

나는 똑같은 일을해야했고 그것을하는 방법을 만들었습니다.

// 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
완벽한 대답 ... 이것은 아직 캔버스에 고유하지 않습니까?! 감사.
andygoestohollywood

1
코드에는 버그가 있으며 채우기 후에 스트로크를 수행해야합니다. 그렇지 않으면 작은 사각형에서는 채우기가 스트로크를 덮어 씁니다.
Zig Mandel

2
나는 예제를 가지고 있지 않지만 코드에서 테스트 한 경우 순서를 수정해야했습니다. 논리적으로, rect를 아직 채우지 않은 경우 어떻게 정확하게 (rect 배경색을 사용하여 부드럽게) 스트로크 할 수 있습니까?
Zig Mandel

2
@ 주안 내 나쁜 이봐, 나는 블로그 게시물을 발견하고 그 tidbit를 잡았다. 편집을 취소하려고했습니다. Goodjob man + 1'd you! 😁
fabbb

6
지그 만델 (Zig Mandel)은 정확합니다. 그 이유는 획을 칠한 다음 채우면 선 너비가 절반으로 줄어들 기 때문입니다. 실제로 두꺼운 선 너비 (예 : 20)로 시도하고 배경색으로 채워진 둥근 사각형을 채워지지 않은 둥근 사각형과 비교하십시오. 채워진 것의 선 너비는 채워지지 않은 것의 선 너비의 절반이됩니다.
Andrew Stacey

106

@jhoff의 솔루션으로 시작했지만 너비 / 높이 매개 변수를 사용하도록 다시 작성했으며을 사용 arcTo하면 꽤 간결합니다.

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

또한 컨텍스트를 반환하여 체인을 조금 연결할 수 있습니다. 예 :

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

4
좋은 솔루션을 제외하고는 캔버스 렌더링 컨텍스트를 망칠 수 없습니다.
Ash Blue

이 솔루션의 문제점은 각 코너의 반경을 독립적으로 제어 할 수 없다는 것입니다. 충분히 유연하지 않습니다. 아래 내 솔루션을 참조하십시오.
Corgalore

1
누군가가 왼쪽 상단 모서리에있는 사각형이 필요한 경우 (x,y)컨텍스트를 저장하고에 번역을 추가 (-w/2,-h/2)하고 컨텍스트를 복원합니다.
nessa.gp

고맙습니다. 이것은 지금까지 나를 위해 일한 유일한 것이고, 다른 사람들은 반경이 높이 또는 너비보다 크거나 클 때 문제를 발생시킵니다. 구현되었습니다!
Howzieky

1
이 솔루션은 모든 다각형에 모서리가 둥글게되도록합니다. 바이올린 .
Doguleez 2016 년

23

Juan, 각 사각형 모서리 반경을 개별적으로 변경할 수 있도록 방법을 약간 개선했습니다.

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

다음과 같이 사용하십시오.

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
이 접근 방식은 둥근 모서리를 많이 제어합니다. 이것이 왜 대답이되지
않습니까?

@VighneshRaut 아마도이 답변은 원래 수락 된 답변을 복사 / 붙여 넣기하고 둥근 모서리를 추가했기 때문일 것입니다. 나는 그것을 받아 들인 대답에 통합 하여이 대답을 인정했습니다. 허용되는 답변에는 실제 예가 있으며 동일한 모서리 (가장 일반적인 경우)의 모든 모서리를 원하면 구문이 더 간단합니다. 마지막으로,이 답변은 no-no 인 기본 객체의 프로토 타입 수정을 제안합니다
Juan Mendes

12

drawPolygon아래 함수 그리는 데 사용할 수 있는 모서리가 둥근 다각형.

여기에서 실행되는 것을 참조하십시오.

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

이 함수는 다음과 같이 다각형 점이있는 배열을받습니다.

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

이것은 포트이며 여기에 게시 된 솔루션의보다 일반적인 버전입니다 .


9

여기 내가 쓴 것이 있습니다 ... 반경을보다 잘 제어하기 위해 2 차 곡선 대신 호를 사용합니다. 또한 쓰다듬어 채우고 떠납니다.

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

예를 들면 다음과 같습니다.

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

이것이 어떻게 반경을 더 잘 제어 할 수있게합니까? 나는 당신이 (타원형 모서리) X / Y 반경을 허용하는 것, 또한 각 모서리에 대해 서로 다른 반경을 지정하는 줄 알았는데
후안 멘데스에게

3
당신은 r2d아마 전화를 원합니다 d2r.
Grumdrig

1
@JuanMendes :이 솔루션에서 둥근 모서리의 (아크 기반) 모양은 (이차 기반) 솔루션의 모양보다 원형입니다. 나는 이것이 "더 나은 반경 제어"라는 의미라고 생각합니다.
브렌트 브래드 번

또한이 방법을 사용했는데 quadraticCurve를 사용하는 것보다 낫습니다. 그러나 직사각형보다 복잡한 것을 그리면 정말 고통 스럽습니다. 안드로이드 캔버스와 같은 자동 방법이있었습니다.
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();

이것은 내가 찾던 것과 정확히
Daniel

실제로 실제로 작동하는 간단하고 포괄적 인 답변입니다. 감사.
Franz Skuffka

5

따라서 이것은 lineJoin = "round"사용을 기반으로하며 적절한 비율, 수학 및 논리 로이 기능을 수행 할 수 있었지만 완벽하지는 않지만 도움이되기를 바랍니다. 각 모서리의 반경을 다르게 하려면 https://p5js.org/reference/#/p5/rect를 확인하십시오.

여기에 간다 :

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에 오신 것을 환영합니다! 이 코드는 문제를 해결할 수 있으므로 작동 방식에 대한 추가 설명을 추가하면 좋습니다.

3

오페라, 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을 사용하기 때문에 레거시 경우에도 유효합니다.


3

캔버스 컨텍스트를 사용하는 일반적인 방법과 함수의 일관성을 높이기 위해 캔버스 컨텍스트 클래스를 확장하여 ' fillRoundedRect'메소드 를 포함하도록 확장 할 수 있습니다 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
} 

이 코드는 캔버스 컨텍스트 객체의 생성자 프로토 타입에 ' fillRoundedRect'속성이 포함되어 있는지 확인 하고 처음으로 하나를 추가합니다. fillRect메소드 와 같은 방식으로 호출됩니다 .

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

이 방법은 arcToGrumdring과 같은 방법을 사용합니다 . 이 방법 this에서 ctx객체에 대한 참조 입니다. 정의되지 않은 경우 획 인수의 기본값은 false입니다. fill 인수는 기본적으로 정의되지 않은 경우 사각형을 채우도록 기본 설정됩니다.

(Firefox에서 테스트 한 결과 모든 구현에서 이러한 방식으로 확장을 허용하는지 알 수 없습니다.)


1
rad = Math.min( rad, ww/2, hh/2 );@Grumdrig 버전에서와 같이 큰 반경에서 작동하도록 추가하는 것이 좋습니다 .
브렌트 브래드 번

3

다음은 lineJoin 을 사용 하여 모서리를 둥글게 만드는 솔루션 입니다. 단단한 모양이 필요하지만 테두리 반경보다 작은 얇은 테두리가 필요한 경우에는 효과가 없습니다.

    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

모서리를 둥글게하려면이 줄을 추가해보십시오. ctx.lineCap = "round";


1
안녕하세요, 스택 오버플로에 오신 것을 환영합니다. 봐 가지고 여기를 . 이것이 직사각형에 유용한 대답입니까?
Jeroen Heier
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.