如何在html5画布中绘制椭圆形?


81

似乎没有绘制椭圆形形状的本机功能。我也不是在寻找蛋形。

是否可以绘制具有2个贝塞尔曲线的椭圆形?有人遇到过吗?

我的目的是画些眼睛,实际上我只是使用弧线。提前致谢。

因此,scale()更改所有下一个形状的缩放比例。Save()之前保存设置,还原用于还原设置以绘制新形状而无需缩放。

感谢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
您不能使用贝塞尔曲线绘制椭圆,这与下面的一些答案不同。您可以使用多项式曲线来近似椭圆,但是不能精确地再现它。

我给了+1,但是这使直线和圆圈失真了,这对我不起作用。很好的信息。
mwilcox 2012年

1
@mwilcox-如果您放到restore()前面,stroke()它不会使行变形,正如Johnathan Hebert在下面对Steve Tranby的回答的评论中提到的那样。另外,这是不必要的,并且对的使用不正确closePath()。这种误解的方法... stackoverflow.com/questions/10807230/...
布莱恩McCutchon

Answers:


113

更新:

  • 缩放方法会影响笔触宽度的外观
  • 正确的缩放方法可以保持笔触宽度完整
  • 画布具有Chrome现在支持的椭圆方法
  • 向JSBin添加了更新的测试

JSBin测试示例(已更新以测试其他人的答案以进行比较)

  • 贝塞尔曲线-基于包含矩形和宽度/高度的左上方绘制
  • 带中心的贝塞尔曲线-根据中心和宽度/高度绘制
  • 圆弧和缩放-基于绘制圆和缩放进行绘制
  • 二次曲线-用二次曲线绘制
    • 测试似乎不完全相同,可能是实现
    • 看到oyophant的答案
  • 画布椭圆-使用W3C标准ellipse()方法

原版的:

如果您想要一个对称的椭圆形,您总是可以创建一个半径为半径的圆,然后将其缩放为所需的高度(编辑:请注意,这将影响笔触宽度的外观-请参阅acdameli的答案),但是如果您想完全控制椭圆形这是使用贝塞尔曲线的一种方法。

<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
这是最通用的解决方案,scale会使笔画变形(请参阅acdameli的回答)。
Trevor Burnham

4
ctx.stroke()可以替换为ctx.fill()以具有填充形状。
h3xStream 2011年

13
如果您只是先缩放,然后使用arc或其他方法添加到路径,再缩放回原始路径,然后再
绘制

1
在许多地方,只有4 *(sqrt(2)-1)/ 3的近似值,其中Google的计算结果为0.55228475。快速搜索即可获得此优质资源:whizkidtech.redprince.net/bezier/circle/kappa
Steve Tranby

1
这不是一个愚蠢的问题。如果您想象一个包围椭圆的矩形,则x,y是左上角,中心将在点{x + w / 2.0,y + h / 2.0}
Steve Tranby 2013年

45

这是其他地方解决方案的简化版本。我画了一个标准的圆圈,平移和缩放,然后描边。

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
这很优雅。让我们contextscale
2013年

1
注意中风之前非常重要的恢复(否则,中风宽度会失真)
Jason S

正是我要的东西,谢谢!我知道除了bezier方式以外,还有其他可能,但是我无法扩大规模来自己工作。

@JasonS只是好奇,如果您在笔触后恢复,为什么笔触宽度会失真?
OneMoreQuestion

17

现在有一个画布的本机椭圆函数,与arc函数非常相似,尽管现在我们有两个半径值和一个很棒的旋转。

椭圆(x,y,radiusX,radiusY,旋转,startAngle,endAngle,逆时针)

现场演示

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

目前似乎只能在Chrome中使用


2
是的,这一步的实施很慢。

@Epistemex同意。我想/希望它会随着时间的推移变得更快。
洛塔克

根据developer.mozilla.org/zh-CN/docs/Web/API/…的支持,Opera现在也支持它
jazzpi 2014年

16

贝塞尔曲线方法非常适合简单的椭圆形。要进行更多控制,可以使用循环绘制一个具有不同x和y半径值(半径,半径?)的椭圆。

添加rotationAngle参数可使椭圆围绕其中心旋转任意角度。可以通过更改循环运行的范围(var i)来绘制部分椭圆。

通过这种方式渲染椭圆,可以确定直线上所有点的确切x,y位置。如果其他对象的位置取决于椭圆的位置和方向,这将很有用。

这是代码示例:

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

在此处查看交互式示例:http : //www.scienceprimer.com/draw-oval-html5-canvas


此方法产生数学上最正确的椭圆。我想知道它在计算成本上如何与其他方法相比。
埃里克(Eric)

@Eric在我的头顶上,看起来(方法:12个乘法,4个加法和8个trig函数)与(4个立方贝塞尔曲线:24个乘法和24个加法)之间的区别就在这里。并假设画布使用De Casteljau迭代方法,我不确定它们是如何在各种浏览器画布中实际实现的。
DerekR

尝试在Chrome中剖析这2种方法:4个立方
贝塞尔曲线

显然,并非所有浏览器都支持CanvasRenderingContext2D.ellipse()。此例程效果很好,精度很高,足以用于车间模板。 甚至可以在Internet Explorer上使用。
zipzit

12

您需要4个贝塞尔曲线(和一个幻数)才能可靠地再现椭圆。看这里:

www.tinaja.com/glib/ellipse4.pdf

两个贝塞尔曲线不能准确地再现椭圆。为了证明这一点,请尝试使用上面两个高度和宽度相等的贝塞尔曲线解决方案中的一些-理想情况下,它们应近似为一个圆,但不会。他们仍然看起来像椭圆形,这证明了他们没有按照他们的预期去做。

这应该可行:

http://jsfiddle.net/BsPsj/

这是代码:

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


}

此解决方案最适合我。四个参数。快。简单。
奥利弗

您好,我也喜欢这种解决方案,但是当我在玩弄您的代码时,发现在参数值的左边添加零时,圆是倾斜的。随着圆圈大小的增加,这一点变得更加突出,您知道为什么会发生这种情况吗? jsfiddle.net/BsPsj/187
alexcons,2013年

1
@alexcons前面加0使其成为八进制整数文字。
阿兰卡·米斯拉

11

您也可以尝试使用非均匀缩放。您可以提供X和Y缩放比例,因此只需将X或Y缩放比例设置为大于其他比例,然后画一个圆,就可以得到一个椭圆。


9

我做的有点不适应这个代码(由安德鲁·Staroscik部分介绍)的人谁不希望有一个如此普通的椭圆和谁只有更大的半轴和(天文JavaScript的玩具剧情轨道椭圆的偏心率数据好peoplo , 例如)。

在这里,您要记住,可以调整步骤i以在图形中获得更高的精度:

/* 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

我的解决方案与所有这些都有一点不同。我认为最接近的答案是上面最投票的答案,但是我认为这种方式更简洁,更容易理解。

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

然后像这样使用它:

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

小提琴中有两个使用示例,以及使用quadraticCurveTo进行尝试的失败尝试。


该代码看起来不错,但是如果使用它通过传递相同的高度和宽度值来绘制一个圆,则会得到一个类似于带有非常圆角的正方形的形状。在这种情况下是否有办法获得真正的圈子?
德鲁·诺阿克斯

我认为在这种情况下,最好的选择是使用arc()方法,然后可以定义弧的开始和结束角度位置,类似于此处获得最高投票的答案的方式。我认为这也是整体上最好的答案。
jaredwilli 2012年

4

我喜欢上面的贝塞尔曲线解决方案。我注意到比例尺也会影响线宽,因此,如果您尝试绘制一个比长宽的椭圆,则顶部和底部的“侧面”将比左侧和右侧的“侧面”更细...

一个很好的例子是:

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

您应该注意到椭圆的峰和谷处的线宽是左右顶点(顶点?)的一半。


5
如果您只是在笔划操作之前反转比例尺,就可以避免笔划比例尺...因此,将相反的比例尺添加到倒数第二行ctx.scale(0.5, 1);
Johnathan Hebert 2012年


3

Chrome和Opera支持画布2d上下文的椭圆方法,但IE,Edge,Firefox和Safari不支持它。

我们可以通过JS实现ellipse方法,也可以使用第三方polyfill。

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

用法示例:

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

您可以使用canvas-5-polyfill提供椭圆方法。

或者只是粘贴一些js代码以提供椭圆方法:

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

椭圆1

椭圆2

椭圆3

椭圆4


今天最好的答案。由于本机的月食功能仍处于试验阶段,因此具有备用功能绝对很棒。谢谢!
2015年

2

这是创建椭圆形形状的另一种方式,尽管它使用了“ fillRect()”函数,但可以通过更改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

这样,您甚至可以绘制椭圆的线段:

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

由于没有人quadraticCurveTo想到使用更简单的方法,因此我为此添加了解决方案。只需将bezierCurveTo@Steve答案中的调用替换为:

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

您也可以删除closePath。椭圆形看起来有些不同。


0

这是我编写的函数,该函数使用的值与SVG中的椭圆弧相同。X1和Y1是最后一个坐标,X2和Y2是结束坐标,radius是一个数字值,顺时针是一个布尔值。它还假定您的画布上下文已经定义。

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

如果您希望椭圆完全适合矩形内部,则实际上是这样的:

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

确保您canvasContext.fillStyle = 'rgba(0,0,0,0)';不使用此设计。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.