pin_drop当前位置:知识文库 ❯ 图文

多媒体:绘制路径 - 完整教程与代码示例

一、教程简介

路径是 Canvas 绘图的核心机制,几乎所有复杂图形(三角形、多边形、星形等)都需要通过路径来绘制。路径由一系列点、线段和曲线组成,通过 beginPath 开始新路径,使用各种绘图命令定义路径形状,最后通过 fillstroke 进行渲染。本教程将深入讲解路径的创建、管理和渲染方法。


二、核心概念

什么是路径

路径是由一系列子路径(subpath)组成的图形轮廓。每个子路径由一个或多个线段或曲线段连接而成。

路径的生命周期:

  1. beginPath() - 开始新路径,清除之前的路径列表
  2. moveTo() - 将画笔移动到起始点(不画线)
  3. 各种绘图命令 - lineToarcquadraticCurveTo
  4. closePath() - 可选,闭合当前子路径
  5. fill() / stroke() - 渲染路径

路径与子路径

  • 一个路径可以包含多个子路径

  • 每次 moveTo() 开始一个新的子路径

  • closePath() 只闭合当前子路径,不会开始新路径

  • fill() 会自动闭合所有未闭合的子路径

填充规则

Canvas 支持两种填充规则:

  • nonzero(默认):非零环绕规则,常用于普通填充

  • evenodd:奇偶规则,常用于创建镂空效果


三、语法与用法

beginPath - 开始新路径

代码示例

ctx.beginPath();

清除当前路径中的所有子路径,开始一个全新的路径。每次绘制新图形前都应调用此方法,否则新路径会与旧路径合并。

closePath - 闭合路径

代码示例

ctx.closePath();

从当前点绘制一条直线回到当前子路径的起始点,形成一个闭合图形。

fill - 填充路径

代码示例

ctx.fill();
ctx.fill(fillRule);
参数 说明 可选值
fillRule 填充规则 'nonzero'(默认)、'evenodd'

stroke - 描边路径

代码示例

ctx.stroke();

沿路径轮廓绘制线条。

rect - 向路径添加矩形

代码示例

ctx.rect(x, y, width, height);

将矩形添加到当前路径中(与 fillRect/strokeRect 不同,这个需要配合 fill()/stroke() 使用)。

isPointInPath - 检测点是否在路径内

代码示例

ctx.isPointInPath(x, y);
ctx.isPointInPath(x, y, fillRule);

四、代码示例

示例1:路径基础 - 绘制各种多边形

(完整代码示例请参考原始 Markdown 文件)

示例2:填充规则与复合路径

(完整代码示例请参考原始 Markdown 文件)


五、浏览器兼容性

方法 Chrome Firefox Safari Edge IE
beginPath 1+ 1.5+ 2+ 12+ 9+
closePath 1+ 1.5+ 2+ 12+ 9+
fill 1+ 1.5+ 2+ 12+ 9+
fill(fillRule) 21+ 2+ 7+ 12+ 不支持
stroke 1+ 1.5+ 2+ 12+ 9+
isPointInPath 2+ 2+ 5+ 12+ 9+

六、注意事项与最佳实践

1. 每次绘制前调用 beginPath

代码示例

// 错误:不调用 beginPath,路径会累积
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.stroke();

ctx.moveTo(10, 50);
ctx.lineTo(100, 50);
ctx.stroke();
// 第二次 stroke 会同时绘制两条线

// 正确:每次绘制前调用 beginPath
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.stroke();

ctx.beginPath();
ctx.moveTo(10, 50);
ctx.lineTo(100, 50);
ctx.stroke();

2. fill 会自动闭合路径

代码示例

ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.lineTo(55, 80);
// 没有 closePath,但 fill 会自动闭合
ctx.fill();

3. closePath 不是 beginPath

代码示例

// closePath 只是画一条回到起点的线,不会开始新路径
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.lineTo(55, 80);
ctx.closePath(); // 画线回到 (10, 10)
// 此时仍在同一个路径中

4. 使用 isPointInPath 进行碰撞检测

代码示例

canvas.addEventListener('click', function(e) {
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  // 重新构建路径(isPointInPath 检测当前路径)
  ctx.beginPath();
  ctx.arc(150, 150, 50, 0, Math.PI * 2);

  if (ctx.isPointInPath(x, y)) {
    console.log('点击了圆形区域');
  }
});

5. 复合路径减少绘制调用

代码示例

// 推荐:使用复合路径一次填充多个图形
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(50, 10);
ctx.lineTo(30, 50);
ctx.closePath();
ctx.moveTo(70, 10);
ctx.lineTo(110, 10);
ctx.lineTo(90, 50);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.fill(); // 一次调用填充两个三角形

// 不推荐:分别绘制
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(50, 10);
ctx.lineTo(30, 50);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 10);
ctx.lineTo(110, 10);
ctx.lineTo(90, 50);
ctx.closePath();
ctx.fillStyle = 'red';
ctx.fill();

七、代码规范示例

代码示例

// 推荐:封装路径绘制函数
function drawTriangle(ctx, x1, y1, x2, y2, x3, y3, options) {
  const { fill, stroke, lineWidth = 1, close = true } = options || {};

  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.lineTo(x3, y3);
  if (close) ctx.closePath();

  if (fill) {
    ctx.fillStyle = fill;
    ctx.fill();
  }
  if (stroke) {
    ctx.strokeStyle = stroke;
    ctx.lineWidth = lineWidth;
    ctx.stroke();
  }
}

// 推荐:正多边形绘制函数
function drawRegularPolygon(ctx, cx, cy, radius, sides, options) {
  const {
    fill = null,
    stroke = null,
    lineWidth = 1,
    rotation = -Math.PI / 2,
    fillRule = 'nonzero'
  } = options || {};

  ctx.beginPath();
  for (let i = 0; i <= sides; i++) {
    const angle = rotation + (i * 2 * Math.PI / sides);
    const x = cx + radius * Math.cos(angle);
    const y = cy + radius * Math.sin(angle);
    if (i === 0) {
      ctx.moveTo(x, y);
    } else {
      ctx.lineTo(x, y);
    }
  }
  ctx.closePath();

  if (fill) {
    ctx.fillStyle = fill;
    ctx.fill(fillRule);
  }
  if (stroke) {
    ctx.strokeStyle = stroke;
    ctx.lineWidth = lineWidth;
    ctx.stroke();
  }
}

// 使用示例
drawRegularPolygon(ctx, 200, 200, 60, 6, {
  fill: 'rgba(88, 166, 255, 0.3)',
  stroke: '#58a6ff',
  lineWidth: 2
});

八、常见问题与解决方案

常见问题

Q1:为什么绘制了多个图形,但之前的图形也被重新绘制了?

原因:没有调用 beginPath(),新路径与旧路径合并。

解决方案

代码示例

// 每次绘制新图形前调用 beginPath
ctx.beginPath();
ctx.arc(100, 100, 30, 0, Math.PI * 2);
ctx.fill();

ctx.beginPath(); // 必须调用
ctx.arc(200, 100, 30, 0, Math.PI * 2);
ctx.fill();
Q2:fill 和 stroke 的顺序有什么影响?

原因:先填充再描边,描边会覆盖在填充之上;反之亦然。

解决方案

代码示例

// 推荐:先填充再描边
ctx.beginPath();
ctx.arc(100, 100, 30, 0, Math.PI * 2);
ctx.fillStyle = '#58a6ff';
ctx.fill();
ctx.strokeStyle = '#1f6feb';
ctx.lineWidth = 2;
ctx.stroke();
Q3:如何创建镂空效果?

解决方案:使用 evenodd 填充规则:

代码示例

ctx.beginPath();
ctx.rect(50, 50, 200, 200);  // 外框
ctx.arc(150, 150, 50, 0, Math.PI * 2);  // 内圆
ctx.fillStyle = '#58a6ff';
ctx.fill('evenodd');  // 内圆区域被镂空
Q4:closePath 后还需要 moveTo 吗?

原因closePath 只是画一条线回到起点,不会开始新子路径。

解决方案

代码示例

// 闭合后开始新子路径需要 moveTo
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(100, 10);
ctx.lineTo(55, 80);
ctx.closePath(); // 回到 (10, 10)

// 开始新子路径
ctx.moveTo(200, 10); // 新的起点
ctx.lineTo(300, 10);
ctx.lineTo(255, 80);
ctx.closePath();
Q5:如何检测点击是否在某个图形内?

解决方案

代码示例

// 方案1:使用 isPointInPath(需要重建路径)
function isPointInShape(x, y) {
  ctx.beginPath();
  ctx.arc(150, 150, 50, 0, Math.PI * 2);
  return ctx.isPointInPath(x, y);
}

// 方案2:使用 Path2D 对象(更高效)
const circlePath = new Path2D();
circlePath.arc(150, 150, 50, 0, Math.PI * 2);

canvas.addEventListener('click', function(e) {
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;

  if (ctx.isPointInPath(circlePath, x, y)) {
    console.log('点击了圆形');
  }
});

九、总结

路径是 Canvas 绘图的核心机制,关键要点:

  • 1. beginPath:每次绘制新图形前必须调用,清除旧路径

  • 2. closePath:从当前点画线回到起点,形成闭合图形

  • 3. fill:填充路径内部,会自动闭合未闭合的子路径

  • 4. stroke:沿路径轮廓描边

  • 5. 填充规则nonzero(默认)和 evenodd(镂空效果)

  • 6. 复合路径:多个子路径可以一次填充或描边,提高性能

  • 7. Path2D:可复用的路径对象,便于碰撞检测和代码组织

下一教程将介绍如何使用路径绘制线条及线条样式。

标签: Canvas路径 beginPath closePath fill stroke 填充规则

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 多媒体:绘制矩形 - 完整教程与代码示例 下一篇: 多媒体:绘制线条 - 完整教程与代码示例

poll相关推荐