如何在画布中绘制线条

3
我想在画布上以以下方式在圆内画一些线条。
我不知道如何像下面展示的那样画线。但是我有在画布上绘制线条和弧线的基本知识。该怎么做?

你可以通过绘制贝塞尔曲线来实现它。https://dev59.com/tWkw5IYBdhLWcg3wSovA - Mardie
我不想使用手动方式。因为切割(水平和垂直线)取决于用户选择而有所不同。请提供更简单和动态的方式。 - Arunkumar
正如Mardie所说,你可以用贝塞尔曲线来实现这个功能,只需要根据用户输入计算相应的参数即可。 - jing3142
1个回答

10

你可以像评论中建议的那样使用带有控制点的贝塞尔曲线,但是这些可能很难控制(不是说笑话),因为它们不会通过您定义的点,并且您始终需要定义两个控制点。

为了使用实际点实现通过点的线,您需要使用 基数样条

没有内置支持,但我之前为JavaScript和canvas制作了一个实现(代码可以从这里下载,MIT许可证)。

使用此功能,您只需最少定义三个点(以获得简单的曲线),该函数将负责在点之间绘制平滑曲线并设置张力值。

例如,如果您定义了以下三个点:

var pts = [10,100, 200,50, 390,100];

你显然只需要获得一个像这样的简单折线,如果我们想说明这些点(作为比较)。

Lines

使用相同的三个点进行 cardinal spline将会得到这样的结果

Cardinal spline

以下代码生成上面的曲线(不包括显示点坐标的红点):
ctx.beginPath();
ctx.curve(pts);
ctx.stroke();

现在只需要移动点(特别是中心点),曲线就会随之变化。为用户添加一个张力滑块可能会更有优势:将张力增加到0.8,您可以得到如下结果:
ctx.curve(pts, 0.8);

Higher tension

将它降低到0.3,会使其变得不那么平滑,如下图所示:

ctx.curve(pts, 0.3);

Lower tension

除了本文档顶部的链接中提到的其他参数外,如果您想要添加超细控制,可以在点数组中拥有“无限”数量的点。

该实现扩展了画布上下文,但是如果您不想使用它,也可以提取该方法并单独使用。

将其实现为圆形

我希望我正确理解了您的绘图... 要在圆形上使用上述功能,您只需要执行以下操作:

  • 定义范围,即您希望将线条绘制到圆的哪一侧。
  • 定义步骤,即每条线之间的空格。

假设您希望在-70°至70°之间绘制线条,最多5条,您可以这样做:

var ctx = canvas.getContext('2d'),
    cx = canvas.width * 0.5,
    cy = canvas.height * 0.5,
    pts,
    startAngle = -70,
    endAngle = 70,
    lines = 5,
    angle,
    range,
    steps,
    radius = 90,
    delta = 15,
    x, y,
    i;

ctx.lineWidth = 3;
ctx.strokeStyle = '#059';

/// draw arc
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.stroke();

/// calculate angle range normalized to 0 degrees
startAngle = startAngle * Math.PI / 180;
endAngle = endAngle * Math.PI / 180;

range = endAngle  - startAngle;
steps = range / (lines + 1);

/// calculate point at circle (vertical only)
for(i = 1; i <= lines; i++) {
    pts = [];
    /// right side
    x = cx + radius * Math.cos(startAngle + steps * i);
    y = cy + radius * Math.sin(startAngle + steps * i);
    
    pts.push(x, y);

    /// center
    pts.push(cx, y + delta * ((y - cy)/ cy));
    
    /// flip for left side
    x = cx - (x - cx);

    pts.push(x, y);

    ///  draw curve
    ctx.beginPath();
    ctx.curve(pts, 0.8);
    ctx.stroke();
}

这将导致这样的结果:

Globe

在这里放置代码

现在只需要尝试不同的值(例如delta)并计算水平行即可。我会把这些留给提问者自己练习:

使用椭圆边缘绘制线条

话虽如此,如果你希望地球更加圆形,你也可以使用一个函数来计算部分椭圆,并将其绘制为线条。这个实现与上面的方法大致相同,但需要使用一个子函数来计算左右两侧之间的椭圆,并使用线条和中心点之间的距离作为半径。

例如:

/// calculate point at circle (vertical only)
for(i = 1; i <= lines; i++) {
    pts = [];
    /// right side
    x = cx + radius * Math.cos(startAngle + steps * i);
    y = cy + radius * Math.sin(startAngle + steps * i);
    
    pts.push(cx - radius, cy);
    pts.push(cx, y);   
    pts.push(cx + radius, cy);

    ///  draw ellipse side
    ctx.beginPath();
    drawEllipseSide(pts, true);
    ctx.stroke();
}

然后在该方法中(仅显示垂直):
function drawEllipseSide(pts, horizontal) {

    var radiusX,
        radiusY,
        cx, cy,
        x, y,
        startAngle,
        endAngle,
        steps = Math.PI * 0.01,
        i = 0;
    
    if (horizontal) {
    
        radiusX = Math.abs(pts[4] - pts[0]) * 0.5;
        radiusY = pts[3] - pts[1];
        cx = pts[2];
        cy = pts[1];
        startAngle = 0;
        endAngle = Math.PI;
        
        x = cx + radiusX * Math.cos(startAngle);
        y = cy + radiusY * Math.sin(startAngle);
        
        ctx.moveTo(x, y);
        
        for(i = startAngle + steps; i < endAngle; i += steps) {
            x = cx + radiusX * Math.cos(i);
            y = cy + radiusY * Math.sin(i);
            ctx.lineTo(x, y);
        }
    }
}

以下是结果(在最终图中我有点作弊,以便更清晰地展示如果您按照这些给定的线路继续进行,最终结果将是什么(没有双关语,我受到了双关语的诅咒):)

Globe

在此插入Fiddle示例

我的代码强迫症作祟 :-P 但是您至少应该有几个选项可供选择。研究代码以了解如何计算垂直线,并采用该方法来计算水平线。

希望这可以帮助您!


+1 很棒的效果,而且还可以调整(半径等)。是的,非常不错! - markE

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接