在前端开发中,我们习惯于使用 HTML 标签(如 <img>, <div>, <svg>)来声明我们想要显示的内容,然后由浏览器负责布局和渲染
但 <canvas> 元素截然不同
Canvas 提供的是一个"即时模式"的 2D (或 3D) 绘图 API。它是一块空白的位图画布,你通过 JavaScript 发出绘图指令(如"画个圈"、"填充颜色"),它就立即执行。它不会记住你画了什么对象;一旦像素被涂上,它就只是一堆像素
1. 渲染图片
drawImage() 方法可以接受多种图像源,包括 <img> 元素、另一个 <canvas> 元素、<video> 的当前帧或 ImageBitmap。
渲染流程:
- 
等待数据就绪: JavaScript 加载图片是异步 的。在 img.onload事件触发后,确保图片数据(像素)已经完全加载到内存中
- 
执行 drawImage:- ctx.drawImage(image, dx, dy): 将源图像的像素数据,原封不动地"复制"到 Canvas 画布的- (dx, dy)位置。
- ctx.drawImage(image, dx, dy, dWidth, dHeight): 在复制前,先对源图像的像素进行缩放 (拉伸或压缩)到- dWidthx- dHeight大小,然后再绘制。
- ctx.drawImage(image, sx, sy, sWidth, sHeight, ...): 这是最复杂的形式,它先从源图像上"裁剪"出一块矩形区域,然后(可选)缩放,最后"粘贴"到画布上。
 
            
            
              js
              
              
            
          
          // 示例:加载并绘制一张图片
const img = new Image();
img.src = 'path/to/image.png';
// 必须等待图片解码完成
img.onload = () => {
  // 将图片像素"印"在 (10, 10) 坐标
  ctx.drawImage(img, 10, 10);
};2. 渲染富文本
Canvas 提供了两个基本方法来绘制文本:ctx.fillText() 和 ctx.strokeText()
例如:<span>Hello <b>World</b></span> 的混合样式和自动换行
Canvas 原生 API 并不支持这些
Canvas 的 fillText 命令只是将一个纯文本字符串,根据当前的 ctx.font、ctx.fillStyle 等样式,对这些形状执行一次填充操作
如何渲染真正的"富文本"?
- 
自动换行: - 将长文本分割成单词
- 逐个单词测量其宽度 (ctx.measureText(word).width)
- 如果当前行宽度超过了设定的最大宽度,你就必须手动增加 y坐标(换行),然后从新行开始绘制
 
- 
混合样式(例如 Hello <b>World</b>):- ctx.font = '16px Arial';
- ctx.fillText('Hello ', x, y);
- 计算 "Hello " 的宽度:const w = ctx.measureText('Hello ').width;
- 更改状态: ctx.font = 'bold 16px Arial';
- 在后面继续绘制: ctx.fillText('World', x + w, y);
 
3. 渲染 SVG
SVG(可缩放矢量图形)和 Canvas 在某种理解下可以是对立的
- SVG 是"声明式": 用 XML 描述一个"场景"(例如,这里有个圆,那里有条线)。浏览器会记住这些对象
- Canvas 是"命令式": 发出"画圆"的指令,它画完就忘了
因此,Canvas 不能直接渲染 SVG 字符串,你必须在两者之间进行"翻译"
方式一:作为图片"光栅化"(最常用)
这是最简单的方法:把 SVG 当作一张普通的 <img> 图片来处理
- 
将 SVG 转换为图像源: - 
如果是 .svg文件:img.src = 'icon.svg';
- 
如果是 SVG 字符串: const svgString = '...'; const svgDataUri = 'data:image/svg+xml;base64,' + btoa(svgString); img.src = svgDataUri; 
 
- 
- 
等待加载: img.onload = () => { ... }
- 
绘制图像: ctx.drawImage(img, dx, dy, width, height);
在 drawImage 被调用的那一刻,SVG 的矢量特性就丢失 了。它被"光栅化"成了指定 width 和 height 的像素。如果你在 Canvas 上放大它,它会像普通图片一样变模糊
方式二:作为矢量"解析转译"(复杂)
这个方法会保留矢量特性
- 
解析 SVG 的 XML 结构 
- 
遍历 SVG 节点(如 <rect>,<circle>,<path>)
- 
将每个 SVG 节点的属性翻译成等效的 Canvas API 调用 - <rect x="10" ...>->- ctx.rect(10, ...)
- 等等
 
4. 渲染 SVG 中的 Path 路径
SVG 的 <path> 元素使用 d 属性来定义极其复杂的形状,例如:
<path d="M10 10 L100 100 C150 150 200 150 250 100 Z">
M 代表 moveTo(移动到),L 代表 lineTo(画线到),C 代表贝塞尔曲线,Z 代表闭合路径
Canvas 的 ctx.lineTo 等 API 无法直接读取这个字符串,我们如何"翻译"它?
现代方案:Path2D 对象
现代浏览器提供了一个强大的 Path2D 对象,它就是为了解决这个问题而生的。Path2D 构造函数可以直接接收 SVG 的 d 属性字符串
- 
创建 Path2D对象:JavaScript iniconst svgPathData = "M10 10 L100 100 C150 150 200 150 250 100 Z"; const myPath = new Path2D(svgPathData);
- 
渲染 Path2D 对象: 一旦你有了 myPath 这个对象,Canvas 就可以直接使用它。这个对象已经"预编译"了所有路径指令 JavaScript inictx.strokeStyle = 'blue'; ctx.lineWidth = 3; ctx.stroke(myPath); // 描边这个路径 ctx.fillStyle = 'rgba(255, 0, 0, 0.5)'; ctx.fill(myPath); // 填充这个路径
Path2D 的优势:
- 简洁: 无需手动解析字符串。
- 高效: 浏览器底层负责解析和优化路径。
- 可重用: 你可以创建一次 Path2D对象,然后在不同的位置、以不同的样式反复绘制它,而无需重新定义路径
传统方案:手动解析(库的实现原理)
在 Path2D 出现之前,或者当需要支持旧版浏览器时,唯一的办法就是手动解析这个 d 字符串。
你需要写一个解析器,逐个读取命令 (M, L, C...) 和坐标,然后调用等效的 Canvas API:
JavaScript
            
            
              ini
              
              
            
          
          function parseAndDraw(svgPathData) {
  // 这是一个极度简化的示例
  const commands = svgPathData.split(' '); // 简化的分割
  
  ctx.beginPath(); // **开始 Canvas 路径**
  
  for (let i = 0; i < commands.length; i++) {
    const cmd = commands[i];
    
    if (cmd === 'M') {
      const x = parseFloat(commands[++i]);
      const y = parseFloat(commands[++i]);
      ctx.moveTo(x, y); // 等效 M
    } else if (cmd === 'L') {
      const x = parseFloat(commands[++i]);
      const y = parseFloat(commands[++i]);
      ctx.lineTo(x, y); // 等效 L
    } else if (cmd === 'Z') {
      ctx.closePath(); // 等效 Z
    }
    // ... 还需要处理 C, S, Q, T, A 等等...
  }
  
  ctx.stroke(); // **渲染 Canvas 路径**
}总结
Canvas 本身只是一个低级的"像素绘图仪"。它之所以能渲染高级内容,完全依赖于将这些内容"翻译"成它能理解的指令:
- 渲染图片: 翻译成 drawImage(像素块传输)。
- 渲染富文本: 翻译成 fillText(字形绘制),但布局和换行需要 JavaScript 手动计算。
- 渲染 SVG: 翻译成 drawImage(光栅化)或者解析其所有节点。
- 渲染 SVG Path: 最佳翻译是使用 new Path2D(d)将其转换成 Canvas 原生路径对象,然后再stroke或fill。
理解了这层"翻译"关系,就掌握了 Canvas 渲染万物的核心