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

Posted

技术标签:

【中文标题】如何在 html5 画布中绘制椭圆?【英文标题】:How to draw an oval in html5 canvas? 【发布时间】:2011-01-11 11:34:38 【问题描述】:

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

是否可以用 2 条贝塞尔曲线绘制椭圆? 有人经历过吗?

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

解决方案

所以 scale() 改变了所有下一个形状的缩放比例。 Save() 保存之前的设置,restore 用于恢复设置以绘制新形状而不缩放。

感谢贾尼

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

【问题讨论】:

您不能使用贝塞尔曲线绘制椭圆,这与下面的一些答案不同。您可以用多项式曲线逼近椭圆,但不能精确再现它。 我给了+1,但这会扭曲线条以及对我不起作用的圆圈。不过信息很好。 @mwilcox - 如果您将restore() 放在stroke() 之前,它不会扭曲线条,正如Johnathan Hebert 在下面对Steve Tranby 回答的评论中提到的那样。此外,这是对closePath() 的不必要和错误使用。这种被误解的方法……***.com/questions/10807230/… 【参考方案1】:

更新:

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

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

贝塞尔曲线 - 基于左上角绘制,包含矩形和宽度/高度 带中心的贝塞尔曲线 - 基于中心和宽度/高度绘制 圆弧和缩放 - 基于绘制圆和缩放绘制 见Deven Kalra's回答 二次曲线 - 用二次曲线绘制 测试看起来不太一样,可能是实现 见oyophant's回答 Canvas Ellipse - 使用 W3C 标准 ellipse() 方法 测试看起来不太一样,可能是实现 见Loktar's回答

原文:

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

<canvas id="thecanvas"  ></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>

【讨论】:

这是最通用的解决方案,因为scale 会扭曲笔画(参见 acdameli 的回答)。 ctx.stroke() 可以替换为 ctx.fill() 以具有填充形状。 如果您只是先缩放,然后使用弧线或其他方式添加到路径,然后缩放回原始路径,然后描边现有路径,不会扭曲笔触 仅 4*(sqrt(2)-1)/3 的近似值发现了很多地方,Google 的计算结果为 0.55228475。快速搜索会产生这个很好的资源:@​​987654325@ 不是一个愚蠢的问题。如果您想像一个包围椭圆的矩形,那么 x,y 是左上角,中心将在点 x + w/2.0, y + h/2.0【参考方案2】:

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

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

【讨论】:

这很优雅。让contextscale 一起完成繁重的工作。 注意非常重要的恢复之前笔画(否则笔画宽度会被扭曲) 正是我想要的,谢谢!我知道除了 bezier 方式之外还有可能,但我自己无法使用规模。 @JasonS 只是好奇如果你在描边后恢复,为什么描边宽度会变形?【参考方案3】:

现在有一个用于画布的原生 ellipse 函数,与 arc 函数非常相似,尽管现在我们有两个半径值和一个很棒的旋转。

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

Live Demo

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

目前似乎只能在 Chrome 中使用

【讨论】:

是的,这个实现很慢。 @Epistemex 同意。我想/希望随着时间的推移它会变得更快。 据developer.mozilla.org/en-US/docs/Web/API/…报道,现在Opera也支持了【参考方案4】:

贝塞尔曲线方法非常适合简单的椭圆形。为了获得更多控制,您可以使用循环绘制具有不同 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 就在我的脑海中,看起来区别在于(此方法:12 次乘法、4 次加法和 8 次三角函数)与(4 三次贝塞尔曲线:24 次乘法和 24 次加法) )。假设画布使用 De Casteljau 迭代方法,我不确定它们是如何在各种浏览器画布中实际实现的。 试图在 Chrome 中分析这 2 种方法:4 立方贝塞尔曲线:约 0.3 毫秒此方法:约 1.5 毫秒,步长 = 0.1 似乎相同的 0.3 毫秒 显然 CanvasRenderingContext2D.ellipse() 并非所有浏览器都支持。这个例程效果很好,非常准确,对于workshop templates. 来说已经足够好了,甚至可以在 Internet Explorer 上运行,叹息。【参考方案5】:

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

【讨论】:

【参考方案6】:

您需要 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 前置一个 0 使其成为八进制整数文字。【参考方案7】:

我对@9​​87654321@(部分由 Andrew Staroscik 提出)进行了一些改编,适用于那些不想要如此普遍的椭圆并且只有椭圆的更大半轴和偏心数据的人(适用于天文 javascript例如,用于绘制轨道的玩具)。

请记住,您可以调整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();

【讨论】:

【参考方案8】:

我的解决方案与所有这些都有些不同。虽然我认为最接近的是上面投票最多的答案,但我认为这种方式更干净,更容易理解。

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() 方法,然后您可以定义弧的开始和结束角度位置,类似于这里投票最多的答案是完毕。我认为这也是总体上最好的答案。【参考方案9】:

我喜欢上面的贝塞尔曲线解决方案。我注意到比例也会影响线宽,所以如果你试图画一个比它高的椭圆,你的顶部和底部“边”会比你的左右“边”看起来更薄......

一个很好的例子是:

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

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

【讨论】:

如果您只是在笔画操作之前反转比例,您可以避免笔画缩放...所以添加相反的缩放作为倒数第二行ctx.scale(0.5, 1);【参考方案10】:

Chrome 和 Opera 支持 ellipse 方法用于画布 2d 上下文,但 IE、Edge、Firefox 和 Safari 不支持。

我们可以通过JS实现椭圆方法,也可以使用第三方的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();
  

【讨论】:

今天的最佳答案。由于本机 eclipse 功能仍处于试验阶段,因此有一个备用功能绝对很棒。谢谢!【参考方案11】:

是的,可以使用两条贝塞尔曲线 - 这是一个简短的教程/示例: http://www.williammalone.com/briefs/how-to-draw-ellipse-html5-canvas/

【讨论】:

请注意,在该页面上给出的方法中,“宽度”实际上并不是生成的椭圆的宽度。见***.com/questions/5694855/…【参考方案12】:

由于没有人想出使用更简单的quadraticCurveTo 的方法,我正在为此添加一个解决方案。只需将@Steve 的answer 中的bezierCurveTo 调用替换为:

  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。不过椭圆形看起来略有不同。

【讨论】:

【参考方案13】:

这是创建类似椭圆形状的另一种方法,尽管它使用“fillRect()”函数,但可用于更改 fillRect() 函数中的参数。

<!DOCTYPE html>
<html lang="en">
<head>
    <title>Sine and cosine functions</title>
</head>
<body>
<canvas id="trigCan"  ></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>

【讨论】:

【参考方案14】:

有了它,你甚至可以画出椭圆的片段:

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/

【讨论】:

【参考方案15】:

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

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

【讨论】:

【参考方案16】:

如果你想让椭圆完全适合矩形,它真的是这样的:

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)'; 没有填写此设计。

【讨论】:

以上是关于如何在 html5 画布中绘制椭圆?的主要内容,如果未能解决你的问题,请参考以下文章

如何在HTML5画布上画一个椭圆

js+html5实现canvas绘制椭圆形图案的方法

如何在html5画布中逐步绘制线条动画

HTML5 Canvas - 如何在画布中的图像上绘制矩形

如何获取椭圆边框的坐标

我们如何在 html5 画布上绘制调整大小的图像?