Làm thế nào để vẽ một hình bầu dục trong html5 canvas?


81

Dường như không có một hàm gốc nào để vẽ một hình dạng giống hình bầu dục. Ngoài ra, tôi không tìm kiếm hình dạng quả trứng.

Có thể vẽ một hình bầu dục với 2 đường cong bezier? Ai đó đã hết hạn với điều đó?

Mục đích của tôi là vẽ một số đôi mắt và thực sự tôi chỉ sử dụng các vòng cung. Cảm ơn trước.

Giải pháp

Vì vậy, scale () thay đổi tỷ lệ cho tất cả các hình tiếp theo. Save () lưu cài đặt trước đó và khôi phục được sử dụng để khôi phục cài đặt để vẽ các hình dạng mới mà không cần chia tỷ lệ.

Cảm ơn Jani

ctx.save();
ctx.scale(0.75, 1);
ctx.beginPath();
ctx.arc(20, 21, 10, 0, Math.PI*2, false);
ctx.stroke();
ctx.closePath();
ctx.restore();

7
Bạn không thể vẽ hình elip bằng đường cong Bezier, không giống như một số câu trả lời dưới đây nói. Bạn có thể tính gần đúng một hình elip với các đường cong đa thức, nhưng không thể tái tạo chính xác nó.
Joe

Tôi đã cho một +1, nhưng điều này làm biến dạng đường cùng với vòng tròn không phù hợp với tôi. Thông tin tốt mặc dù.
mwilcox

1
@mwilcox - nếu bạn đặt restore()trước stroke()nó sẽ không làm sai lệch dòng, như Johnathan Hebert đã đề cập trong bình luận cho câu trả lời của Steve Tranby bên dưới. Ngoài ra, đây là việc sử dụng không cần thiết và không chính xác closePath(). Một phương pháp bị hiểu lầm như vậy ... stackoverflow.com/questions/10807230/…
Brian McCutchon

Câu trả lời:


113

cập nhật:

  • phương pháp chia tỷ lệ có thể ảnh hưởng đến sự xuất hiện chiều rộng của nét vẽ
  • phương pháp chia tỷ lệ được thực hiện đúng có thể giữ nguyên chiều rộng nét vẽ
  • canvas có phương thức ellipse mà Chrome hiện hỗ trợ
  • đã thêm các thử nghiệm cập nhật vào JSBin

Ví dụ kiểm tra JSBin (được cập nhật để kiểm tra câu trả lời của người khác để so sánh)

  • Bezier - vẽ dựa trên phía trên bên trái có chứa trực tràng và chiều rộng / chiều cao
  • Bezier with Center - vẽ dựa trên tâm và chiều rộng / chiều cao
  • Cung và Chia tỷ lệ - vẽ dựa trên vẽ vòng tròn và chia tỷ lệ
  • Đường cong bậc hai - vẽ bằng phép toán bậc hai
    • kiểm tra dường như không vẽ hoàn toàn giống nhau, có thể đang thực hiện
    • xem câu trả lời của oyophant
  • Canvas Ellipse - sử dụng phương thức ellipse () tiêu chuẩn W3C
    • kiểm tra dường như không vẽ hoàn toàn giống nhau, có thể đang thực hiện
    • xem câu trả lời của Loktar

Nguyên:

Nếu bạn muốn một hình bầu dục đối xứng, bạn luôn có thể tạo một hình tròn có chiều rộng bán kính, sau đó chia tỷ lệ nó theo chiều cao bạn muốn ( chỉnh sửa: lưu ý điều này sẽ ảnh hưởng đến sự xuất hiện của chiều rộng nét - xem câu trả lời của acdameli), nhưng nếu bạn muốn toàn quyền kiểm soát hình elip đây là một cách sử dụng các đường cong bezier.

<canvas id="thecanvas" width="400" height="400"></canvas>

<script>
var canvas = document.getElementById('thecanvas');

if(canvas.getContext) 
{
  var ctx = canvas.getContext('2d');
  drawEllipse(ctx, 10, 10, 100, 60);
  drawEllipseByCenter(ctx, 60,40,20,10);
}

function drawEllipseByCenter(ctx, cx, cy, w, h) {
  drawEllipse(ctx, cx - w/2.0, cy - h/2.0, w, h);
}

function drawEllipse(ctx, x, y, w, h) {
  var kappa = .5522848,
      ox = (w / 2) * kappa, // control point offset horizontal
      oy = (h / 2) * kappa, // control point offset vertical
      xe = x + w,           // x-end
      ye = y + h,           // y-end
      xm = x + w / 2,       // x-middle
      ym = y + h / 2;       // y-middle

  ctx.beginPath();
  ctx.moveTo(x, ym);
  ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  //ctx.closePath(); // not used correctly, see comments (use to close off open path)
  ctx.stroke();
}

</script>

1
Đây là giải pháp chung nhất, vì scalesẽ làm biến dạng các nét (xem câu trả lời của acdameli).
Trevor Burnham

4
ctx.stroke () có thể được thay thế bằng ctx.fill () để có một hình dạng được tô đầy.
h3xStream

13
quy mô sẽ không đột quỵ distort nếu bạn chỉ cần mở rộng quy mô đầu tiên, sau đó thêm vào con đường sử dụng hồ quang hoặc bất cứ điều gì, sau đó mở rộng trở lại ban đầu, sau đó stroke path hiện
Johnathan Hebert

1
Chỉ một ước tính gần đúng của 4 * (sqrt (2) -1) / 3 đã tìm thấy nhiều nơi, trong đó calc của Google cho 0,55228475. Một tìm kiếm nhanh cho ra nguồn tốt này: whizkidtech.redprince.net/bezier/circle/kappa
Steve Tranby

1
Không phải là một câu hỏi ngu ngốc. Nếu bạn tưởng tượng một hình chữ nhật bao quanh hình elip thì x, y là góc trên cùng bên trái và tâm sẽ ở điểm {x + w / 2.0, y + h / 2.0}
Steve Tranby

45

Đây là phiên bản đơn giản của các giải pháp ở những nơi khác. Tôi vẽ một vòng tròn chính tắc, dịch và chia tỷ lệ và sau đó đột quỵ.

function ellipse(context, cx, cy, rx, ry){
        context.save(); // save state
        context.beginPath();

        context.translate(cx-rx, cy-ry);
        context.scale(rx, ry);
        context.arc(1, 1, 1, 0, 2 * Math.PI, false);

        context.restore(); // restore to original state
        context.stroke();
}

2
Điều này là khá thanh lịch. Hãy contextthực hiện việc nâng nặng với scale.
adu

1
Lưu ý rất quan trọng khôi phục trước khi đột quỵ (nếu không thì đột quỵ chiều rộng bị méo)
Jason S

Chính xác những gì tôi đang tìm kiếm, cảm ơn bạn! Tôi biết điều đó có thể xảy ra ngoài cách dễ dàng hơn, nhưng tôi không thể tự mình đạt được quy mô để làm việc.

@JasonS Chỉ tò mò tại sao chiều rộng nét vẽ sẽ bị bóp méo nếu bạn khôi phục lại sau khi đột quỵ?
OneMoreQuestion

17

Bây giờ có một hàm hình elip riêng cho canvas, rất giống với hàm vòng cung mặc dù bây giờ chúng ta có hai giá trị bán kính và một vòng quay thật tuyệt vời.

hình elip (x, y, radiusX, radiusY, xoay, startAngle, endAngle, ngược chiều kim đồng hồ)

Bản thử trực tiếp

ctx.ellipse(100, 100, 10, 15, 0, 0, Math.PI*2);
ctx.fill();

Có vẻ như chỉ hoạt động trong Chrome hiện tại


2
Vâng, việc triển khai cái này rất chậm.

@Epistemex Đồng ý. Tôi tưởng tượng / hy vọng nó sẽ nhanh hơn theo thời gian.
Loktar

Theo developer.mozilla.org/en-US/docs/Web/API/… , nó hiện cũng được hỗ trợ trong Opera
jazzpi

16

Cách tiếp cận đường cong bezier là rất tốt cho các hình bầu dục đơn giản. Để kiểm soát nhiều hơn, bạn có thể sử dụng vòng lặp để vẽ một hình elip với các giá trị khác nhau cho bán kính x và y (bán kính, bán kính?).

Thêm tham số RotationAngle cho phép xoay hình bầu dục quanh tâm của nó theo bất kỳ góc nào. Có thể vẽ từng phần hình bầu dục bằng cách thay đổi phạm vi (var i) mà vòng lặp chạy qua đó.

Kết xuất hình bầu dục theo cách này cho phép bạn xác định chính xác vị trí x, y của tất cả các điểm trên đường thẳng. Điều này rất hữu ích nếu vị trí của các đối tượng khác phụ thuộc vào vị trí và hướng của hình bầu dục.

Đây là một ví dụ về mã:

for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01 ) {
    xPos = centerX - (radiusX * Math.sin(i)) * Math.sin(rotationAngle * Math.PI) + (radiusY * Math.cos(i)) * Math.cos(rotationAngle * Math.PI);
    yPos = centerY + (radiusY * Math.cos(i)) * Math.sin(rotationAngle * Math.PI) + (radiusX * Math.sin(i)) * Math.cos(rotationAngle * Math.PI);

    if (i == 0) {
        cxt.moveTo(xPos, yPos);
    } else {
        cxt.lineTo(xPos, yPos);
    }
}

Xem ví dụ tương tác tại đây: http://www.scienceprimer.com/draw-oval-html5-canvas


Phương pháp này tạo ra hình elip chính xác nhất về mặt toán học. Tôi đang tự hỏi làm thế nào nó so sánh với những người khác về chi phí tính toán.
Eric

@Eric Ngay trên đầu tôi, có vẻ như sự khác biệt là giữa (Phương pháp này: 12 phép nhân, 4 phép cộng và 8 hàm trig) so với (4 Cubic Beziers: 24 phép nhân và 24 phép cộng). Và đó là giả sử canvas sử dụng phương pháp lặp lại De Casteljau, tôi không chắc chúng thực sự được triển khai như thế nào trong các trình duyệt khác nhau.
DerekR

cố gắng để cấu hình này 2 cách tiếp cận trong Chrome: 4 Beziers Cubic: khoảng 0,3 ms Phương pháp này: khoảng 1,5 ms với bước = 0,1 có vẻ như về cùng 0.3ms
sol0mka

Rõ ràng CanvasRenderingContext2D.ellipse () không được hỗ trợ trên tất cả các trình duyệt. Quy trình này hoạt động tuyệt vời, độ chính xác hoàn hảo, đủ tốt cho các mẫu hội thảo. Thậm chí hoạt động trên Internet Explorer, thở dài.
zipzit

12

Bạn cần 4 đường cong bezier (và một số ma thuật) để tái tạo một cách đáng tin cậy một hình elip. Xem tại đây:

www.tinaja.com/glib/ellipse4.pdf

Hai beziers không tái tạo chính xác một hình elip. Để chứng minh điều này, hãy thử một số trong số 2 giải pháp bezier ở trên với chiều cao và chiều rộng bằng nhau - lý tưởng là chúng nên tính gần đúng một hình tròn nhưng sẽ không. Chúng vẫn sẽ trông hình bầu dục, điều này chứng tỏ chúng không làm những gì chúng phải làm.

Đây là một cái gì đó sẽ hoạt động:

http://jsfiddle.net/BsPsj/

Đây là mã:

function ellipse(cx, cy, w, h){
    var ctx = canvas.getContext('2d');
    ctx.beginPath();
    var lx = cx - w/2,
        rx = cx + w/2,
        ty = cy - h/2,
        by = cy + h/2;
    var magic = 0.551784;
    var xmagic = magic*w/2;
    var ymagic = h*magic/2;
    ctx.moveTo(cx,ty);
    ctx.bezierCurveTo(cx+xmagic,ty,rx,cy-ymagic,rx,cy);
    ctx.bezierCurveTo(rx,cy+ymagic,cx+xmagic,by,cx,by);
    ctx.bezierCurveTo(cx-xmagic,by,lx,cy+ymagic,lx,cy);
    ctx.bezierCurveTo(lx,cy-ymagic,cx-xmagic,ty,cx,ty);
    ctx.stroke();


}

Giải pháp này phù hợp nhất với tôi. Bốn thông số. Nhanh chóng. Dễ dàng.
Oliver

Xin chào, tôi cũng thích giải pháp này, nhưng khi tôi đang nghịch mã của bạn và nhận thấy rằng khi thêm một số 0 vào bên trái giá trị tham số, vòng tròn bị lệch. Điều này được nhấn mạnh khi hình tròn tăng kích thước, bạn có biết tại sao điều này có thể xảy ra không? jsfiddle.net/BsPsj/187
alexcons

1
@alexcons viết trước số 0 làm cho nó trở thành một số nguyên bát phân theo nghĩa đen.
Alankar Misra

11

Bạn cũng có thể thử sử dụng tỷ lệ không đồng nhất. Bạn có thể cung cấp tỷ lệ X và Y, vì vậy chỉ cần đặt tỷ lệ X hoặc Y lớn hơn tỷ lệ kia và vẽ một hình tròn và bạn có một hình elip.


9

Tôi đã thực hiện một chút điều chỉnh của mã này (do Andrew Staroscik trình bày một phần) cho peoplo, những người không muốn một hình elip chung chung và những người chỉ có bán trục lớn hơn và dữ liệu độ lệch tâm của hình elip (tốt cho đồ chơi javascript thiên văn để vẽ quỹ đạo , ví dụ).

Ở đây, hãy nhớ rằng người ta có thể điều chỉnh các bước iđể có độ chính xác cao hơn trong bản vẽ:

/* draw ellipse
 * x0,y0 = center of the ellipse
 * a = greater semi-axis
 * exc = ellipse excentricity (exc = 0 for circle, 0 < exc < 1 for ellipse, exc > 1 for hyperbole)
 */
function drawEllipse(ctx, x0, y0, a, exc, lineWidth, color)
{
    x0 += a * exc;
    var r = a * (1 - exc*exc)/(1 + exc),
        x = x0 + r,
        y = y0;
    ctx.beginPath();
    ctx.moveTo(x, y);
    var i = 0.01 * Math.PI;
    var twoPi = 2 * Math.PI;
    while (i < twoPi) {
        r = a * (1 - exc*exc)/(1 + exc * Math.cos(i));
        x = x0 + r * Math.cos(i);
        y = y0 + r * Math.sin(i);
        ctx.lineTo(x, y);
        i += 0.01;
    }
    ctx.lineWidth = lineWidth;
    ctx.strokeStyle = color;
    ctx.closePath();
    ctx.stroke();
}

5

Giải pháp của tôi hơi khác so với tất cả những giải pháp này. Gần nhất, tôi nghĩ là câu trả lời được bình chọn nhiều nhất ở trên, nhưng tôi nghĩ cách này gọn gàng và dễ hiểu hơn một chút.

http://jsfiddle.net/jaredwilli/CZeEG/4/

    function bezierCurve(centerX, centerY, width, height) {
    con.beginPath();
    con.moveTo(centerX, centerY - height / 2);

    con.bezierCurveTo(
        centerX + width / 2, centerY - height / 2,
        centerX + width / 2, centerY + height / 2,
        centerX, centerY + height / 2
    );
    con.bezierCurveTo(
        centerX - width / 2, centerY + height / 2,
        centerX - width / 2, centerY - height / 2,
        centerX, centerY - height / 2
    );

    con.fillStyle = 'white';
    con.fill();
    con.closePath();
}

Và sau đó sử dụng nó như thế này:

bezierCurve(x + 60, y + 75, 80, 130);

Có một vài ví dụ sử dụng trong fiddle, cùng với một nỗ lực không thành công để tạo một ví dụ bằng cách sử dụng quadraticCurveTo.


Mã trông đẹp, nhưng nếu bạn sử dụng điều này để vẽ một hình tròn bằng cách chuyển các giá trị chiều cao và chiều rộng giống nhau, bạn sẽ có được một hình dạng giống như một hình vuông với các góc rất tròn. Có cách nào để có được một vòng tròn chính hãng trong trường hợp như vậy?
Drew Noakes

Tôi nghĩ lựa chọn tốt nhất trong trường hợp đó là sử dụng phương thức arc (), sau đó bạn có thể xác định vị trí góc bắt đầu và kết thúc cho cung tròn, tương tự như cách thực hiện câu trả lời được bình chọn nhiều nhất ở đây. Tôi nghĩ đó là câu trả lời tốt nhất về tổng thể.
jaredwilli

4

Tôi thích giải pháp đường cong Bezier ở trên. Tôi nhận thấy tỷ lệ cũng ảnh hưởng đến chiều rộng đường thẳng, vì vậy nếu bạn đang cố vẽ một hình elip có chiều rộng hơn chiều cao, "cạnh" trên và dưới của bạn sẽ có vẻ mỏng hơn "cạnh" trái và phải của bạn ...

một ví dụ điển hình sẽ là:

ctx.lineWidth = 4;
ctx.scale(1, 0.5);
ctx.beginPath();
ctx.arc(20, 20, 10, 0, Math.PI * 2, false);
ctx.stroke();

bạn sẽ nhận thấy chiều rộng của đường thẳng ở đỉnh và thung lũng của hình elip rộng bằng một nửa ở đỉnh trái và phải (apices?).


5
Bạn có thể tránh được những tỉ lệ đột quỵ nếu bạn chỉ cần đảo ngược quy mô ngay trước khi phẫu thuật đột quỵ ... để thêm rộng ngược lại là thứ hai để dòng cuối cùngctx.scale(0.5, 1);
Johnathan Hebert


3

Chrome và Opera hỗ trợ phương thức ellipse cho ngữ cảnh canvas 2d, nhưng IE, Edge, Firefox và Safari không hỗ trợ nó.

Chúng ta có thể triển khai phương thức ellipse bằng JS hoặc sử dụng polyfill của bên thứ ba.

ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)

Ví dụ sử dụng:

ctx.ellipse(20, 21, 10, 10, 0, 0, Math.PI*2, true);

Bạn có thể sử dụng canvas-5-polyfill để cung cấp phương pháp hình elip.

Hoặc chỉ cần dán một số mã js để cung cấp phương thức ellipse:

if (CanvasRenderingContext2D.prototype.ellipse == undefined) {
  CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY,
        rotation, startAngle, endAngle, antiClockwise) {
    this.save();
    this.translate(x, y);
    this.rotate(rotation);
    this.scale(radiusX, radiusY);
    this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
    this.restore();
  }
}

hình elip 1

hình elip 2

hình elip 3

hình elip 4


câu trả lời tốt nhất cho ngày hôm nay. Vì chức năng nhật thực gốc vẫn đang trong giai đoạn thử nghiệm, nên việc dự phòng là hoàn toàn tuyệt vời. cảm ơn!
walv,

2

Đây là một cách khác để tạo một hình dạng elip, mặc dù nó sử dụng hàm "fillRect ()", hàm này có thể được sử dụng để thay đổi các đối số trong hàm fillRect ().

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Sine and cosine functions</title>
</head>
<body>
<canvas id="trigCan" width="400" height="400"></canvas>

<script type="text/javascript">
var canvas = document.getElementById("trigCan"), ctx = canvas.getContext('2d');
for (var i = 0; i < 360; i++) {
    var x = Math.sin(i), y = Math.cos(i);
    ctx.stroke();
    ctx.fillRect(50 * 2 * x * 2 / 5 + 200, 40 * 2 * y / 4 + 200, 10, 10, true);
}
</script>
</body>
</html>

2

Với điều này, bạn thậm chí có thể vẽ các đoạn của một hình elip:

function ellipse(color, lineWidth, x, y, stretchX, stretchY, startAngle, endAngle) {
    for (var angle = startAngle; angle < endAngle; angle += Math.PI / 180) {
        ctx.beginPath()
        ctx.moveTo(x, y)
        ctx.lineTo(x + Math.cos(angle) * stretchX, y + Math.sin(angle) * stretchY)
        ctx.lineWidth = lineWidth
        ctx.strokeStyle = color
        ctx.stroke()
        ctx.closePath()
    }
}

http://jsfiddle.net/FazAe/1/


2

Vì không ai nghĩ ra cách tiếp cận sử dụng càng đơn giản quadraticCurveTonên tôi đang thêm một giải pháp cho điều đó. Chỉ cần thay thế các bezierCurveTocuộc gọi trong câu trả lời của @ Steve bằng điều này:

  ctx.quadraticCurveTo(x,y,xm,y);
  ctx.quadraticCurveTo(xe,y,xe,ym);
  ctx.quadraticCurveTo(xe,ye,xm,ye);
  ctx.quadraticCurveTo(x,ye,x,ym);

Bạn cũng có thể xóa closePath. Hình bầu dục trông hơi khác một chút.


0

Đây là một hàm tôi đã viết sử dụng các giá trị giống như cung hình elip trong SVG. X1 & Y1 là tọa độ cuối cùng, X2 & Y2 là tọa độ cuối, bán kính là giá trị số và theo chiều kim đồng hồ là giá trị boolean. Nó cũng giả định rằng ngữ cảnh canvas của bạn đã được xác định.

function ellipse(x1, y1, x2, y2, radius, clockwise) {

var cBx = (x1 + x2) / 2;    //get point between xy1 and xy2
var cBy = (y1 + y2) / 2;
var aB = Math.atan2(y1 - y2, x1 - x2);  //get angle to bulge point in radians
if (clockwise) { aB += (90 * (Math.PI / 180)); }
else { aB -= (90 * (Math.PI / 180)); }
var op_side = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) / 2;
var adj_side = Math.sqrt(Math.pow(radius, 2) - Math.pow(op_side, 2));

if (isNaN(adj_side)) {
    adj_side = Math.sqrt(Math.pow(op_side, 2) - Math.pow(radius, 2));
}

var Cx = cBx + (adj_side * Math.cos(aB));            
var Cy = cBy + (adj_side * Math.sin(aB));
var startA = Math.atan2(y1 - Cy, x1 - Cx);       //get start/end angles in radians
var endA = Math.atan2(y2 - Cy, x2 - Cx);
var mid = (startA + endA) / 2;
var Mx = Cx + (radius * Math.cos(mid));
var My = Cy + (radius * Math.sin(mid));
context.arc(Cx, Cy, radius, startA, endA, clockwise);
}

0

Nếu bạn muốn hình elip vừa vặn hoàn toàn bên trong một hình chữ nhật, nó thực sự như thế này:

function ellipse(canvasContext, x, y, width, height){
  var z = canvasContext, X = Math.round(x), Y = Math.round(y), wd = Math.round(width), ht = Math.round(height), h6 = Math.round(ht/6);
  var y2 = Math.round(Y+ht/2), xw = X+wd, ym = Y-h6, yp = Y+ht+h6, cs = cards, c = this.card;
  z.beginPath(); z.moveTo(X, y2); z.bezierCurveTo(X, ym, xw, ym, xw, y2); z.bezierCurveTo(xw, yp, X, yp, X, y2); z.fill(); z.stroke();
  return z;
}

Hãy chắc chắn rằng bạn canvasContext.fillStyle = 'rgba(0,0,0,0)';không điền vào thiết kế này.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.