musicode / test

test
14 stars 1 forks source link

[Canvas 学习笔记]绘图 - 圆弧 #30

Open musicode opened 9 years ago

musicode commented 9 years ago

相关 API:

(x, y) 表示圆心坐标,radius 表示圆的半径,startRadian 表示开始弧度,endRadian 表示结束弧度, counterClockwise 表示是否逆时针绘制。

弧度和角度的转换公式如下:

angle = Math.PI / 180

radian = 180 / Math.PI

// 逆时针
context.beginPath();
context.arc(90, 75, 50, 0, 45 * Math.PI / 180, true);
context.stroke();

// 顺时针
context.beginPath();
context.arc(210, 75, 50, 0, 45 * Math.PI / 180, false);
context.stroke();

// 画圆
context.beginPath();
context.arc(330, 75, 50, 0, 360 * Math.PI / 180, false);
context.stroke();

arc

arcTo

arcTo 的方法签名看似需要两个点 (x1, y1) 和 (x2, y2),其实还需要一个点:当前路径的最后一个点

也就是说,下面这种方式是无效的,因为少了一个当前路径的最后一个点:

context.beginPath();
context.arcTo(100, 50, 100, 100, 50);
context.stroke();

加上当前点试试:

context.beginPath();
context.moveTo(50, 50);
context.arcTo(100, 50, 100, 100, 50);
context.stroke();

context.beginPath();
context.moveTo(100, 50);
context.arcTo(200, 50, 200, 100, 30);
context.stroke();

arcTo

如果不清楚到底是怎么画出来的,可以按如下方式理解:

  1. 从当前路径的最后一个点到 (x1, y1) 画一条直线 l1,再从 (x1, y1) 到 (x2, y2) 画一条直线 l2
  2. 以 radius 为半径画圆,这个圆与 l1 l2 相切
  3. 圆弧的起点是 l1 与圆的切点 p1,圆弧的终点是 l2 与圆的切点 p2
  4. 如果当前路径的最后一个点不是 p1,会从该点到 p1 画一条直线

arcTo 的这个特性,非常适合绘制圆角矩形。

function drawRoundedRect(x, y, width, height, radius) {

    context.beginPath();
    context.moveTo(x + radius, y);

    context.arcTo(
        x + width,
        y,
        x + width,
        y + radius,
        radius
    );

    // 由上面的列表项 4 可知,无需调用 lineTo
    context.arcTo(
        x + width,
        y + height,
        x + width - radius,
        y + height,
        radius
    );

    context.arcTo(
        x,
        y + height,
        x,
        y + height - radius,
        radius
    );

    context.arcTo(
        x,
        y,
        x + radius,
        y,
        radius
    );

    context.stroke();
}

drawRoundedRect(50, 50, 100, 60, 10);

圆角矩形

填充算法

填充算法

当路径存在相交时(想象一下九连环...),调用 fill() 方法会根据某种算法计算需要填充哪些部分。

以上图举例,两个同心圆,一个顺时针绘制,一个逆时针绘制,结果就是中间镂空。

上图画了两条线,一条从中心向左下画到圆外,一条从两圆之间画到圆外。这两条线旁边加上了数字,这些数字可判断是否需要填充。

算法如下:

  1. 某区域需要判断是否填充,可从该区域向外画一条线,一直画到最外面,并给该线条一个计数器,初始值为 0
  2. 线条会与图形的路径相交,与顺时针路径相交计数器加 1,与逆时针相交计数器减 1
  3. 最后判断该线条的计数器是否为 0,如果为 0,不填充,如果不为 0,填充。

demo

扩展

绘制每种图形都有顺时针逆时针之分,有些方法原生提供了方向参数,比如 arc,大多数方法没有提供,但并不意味着它们没有方向。

比如 rect,不像圆弧可以从某角度到某角度,矩形总是完整绘制的,所以 rect 无需提供方向参数,默认就是顺时针

如果想绘制逆时针的矩形,可自行实现 drawRect 方法:

function drawRect(x, y, width, height, counterClockwise) {
    context.moveTo(x, y);
    if (counterClockwise) {
        context.lineTo(x, y + height);
        context.lineTo(x + width, y + height);
        context.lineTo(x + width, y);
    }
    else {
        context.lineTo(x + width, y);
        context.lineTo(x + width, y + height);
        context.lineTo(x, y + height);
    }
    context.closePath();
}

调用时,可传入方向值:

context.beginPath();
drawRect(50, 50, 100, 60, true);
context.stroke();

扩展

如图所示,大矩形用 rect 绘制,内部矩形用 drawRect 绘制,方向为逆时针,最后出现了镂空效果。

demo