2026-02-22 01:21:39 +08:00
|
|
|
|
# 图形与动画(Canvas 与他的朋友们)
|
2026-02-14 12:14:07 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
::: tip 🎯 核心问题
|
2026-02-14 12:14:07 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
以前的网页只能展示干巴巴的文字和图片。但如果你想做打砖块游戏、华丽的动态特效、或是可以自由拖拽的数据报表,仅仅靠 `<div>` 是远远不够的。这就是 **Canvas(画布)** 诞生的原因。
|
|
|
|
|
|
|
|
|
|
|
|
本指南将带你从画下第一条线开始,一路打怪升级,最终亲手写出能在浏览器中流畅运行 60 帧的粒子引擎。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-14 12:14:07 +08:00
|
|
|
|
:::
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
## 1. 什么是 Canvas?
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
如果说早期的网页是用**乐高积木**(HTML 标签)拼凑起来的静态模型,那么 HTML5 的 `<canvas>` 标签就是扔给你一张**巨大的数字白纸**,然后递给你一支靠代码控制的**画笔**,剩下的全交给你自由发挥。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
这里面的画没有任何标签结构。你用画笔涂上去的心血,一旦落笔就变成了最纯粹的**“像素颜料”**。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
### 1.1 Canvas vs SVG:两种不同流派的艺术家
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
在前端画图界,Canvas 有个宿敌叫 **SVG**。它们代表了两种截然不同的绘画观念:
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
- **Canvas(位图画板):**
|
|
|
|
|
|
- **原理**:就像真实在纸上涂色,几笔画上去就变成一团颜料(像素点)。
|
|
|
|
|
|
- **优势**:电脑只管往屏幕上“洒颜料”,性能起飞!能同时画出大几千个活蹦乱跳的闪烁粒子。
|
|
|
|
|
|
- **缺点**:画完就没法单独反悔(没法通过 DOM 节点选择),且放大会造成马赛克发虚。
|
|
|
|
|
|
- **SVG(矢量图拼接):**
|
|
|
|
|
|
- **原理**:就像做 PPT。你画一个圆,它就生成一个独立标签的“圆实体”放在画面上。
|
|
|
|
|
|
- **优势**:不管放大 100 倍还是 10 万倍,永远极其清晰。每个形状都是独立的 DOM 节点,你可以随时用 CSS 和 JS 改变它的颜色或绑定点击事件。
|
|
|
|
|
|
- **缺点**:如果你试图放几万个对象乱飞,繁重的 DOM 树和排版引擎会直接把浏览器卡死。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
**🎮 简单总结:玩动态游戏、做酷炫粒子特效用 Canvas;画精密的 Logo、写交互清晰的小图表用 SVG。**
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
## 2. 第一笔:理解反直觉的坐标系
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
### 2.1 这张纸的上下怎么颠倒了?
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
当你准备下笔时,得先明白 Canvas 里的尺子是反着的。对于传统的数学课坐标系,中心点零点在中间,越往上越大。但在计算机屏幕显示领域,几乎所有设备的“原点(0,0)”都定在**屏幕的最左上角**。向右走 X 轴变大没问题,但是**向下走,Y 轴变大。**
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
**Canvas 坐标系统的核心原则:**
|
|
|
|
|
|
- **原生单位:** 像素 (px),与屏幕物理像素 1:1 对应。
|
|
|
|
|
|
- **X 轴:** 向右为正方向,从 `0` 到 `canvas.width`。
|
|
|
|
|
|
- **Y 轴:** 向下为正方向,从 `0` 到 `canvas.height`。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
👇 拖拽下面的小圆点,直观感受计算机图形学中的坐标原点与走向:
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
<CoordinateSystemDemo />
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
### 2.2 给你的魔法画笔上调料
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
有了坐标体系,我们就能召唤画笔了(代码中称为 `Context`,或缩写 `ctx`)。就如同拿着真实的调色盘作画,Canvas 的 API 设计完美遵循了物理作画的三个步骤:
|
|
|
|
|
|
|
|
|
|
|
|
1. **调色(State)**:通过 `fillStyle` 设置填充色,`strokeStyle` 设置描边色。
|
|
|
|
|
|
2. **构形(Path)**:构思你是要画一条线(`lineTo`)、还是一个圆(`arc`)、亦或一个矩形(`rect`)。
|
|
|
|
|
|
3. **极简下笔(Render)**:决定是内部填充(`fill()`)还是勾勒边缘(`stroke()`)。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
由于 Canvas 是纯粹的位图画布,“落子无悔”,你一旦画下,它立刻干涸成为像素,无法再被撤销为独立对象。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
👇 尝试在下面的演示中挑选不同形状和颜色,看看背后的代码是如何执行上述“三步走”的:
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
<CanvasBasicsDemo />
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
## 3. 翻页动画书:如何让画面动起来极度丝滑
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
既然 Canvas 一旦填色就变成了永久的像素,那么各种 HTML5 页游里满屏乱跑的角色是怎么做出来的?
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
答案是**“骗过你的眼睛”**。这和手翻动画书或者电影胶片的原理一模一样。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
1. **擦黑板(Clear):** 用 `clearRect()` 把整块画布上的内容毫不留情地清空。
|
|
|
|
|
|
2. **计算新位置(Update):** 让角色的 X 坐标往前偷偷加 2 个像素点。
|
|
|
|
|
|
3. **下笔重画(Render):** 把角色在新的位置重新画一次。
|
|
|
|
|
|
4. **疯狂循环(Loop):** 结合浏览器内置的极其精准的节拍器 `requestAnimationFrame`。它会以显示器的刷新率(通常是每秒 60 次,即 60 FPS)重复这三个动作。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
由于人眼自带“视觉残留”,在每秒 60 次的【擦除 -> 更新 -> 重绘】中,你看到的不仅不是闪烁的黑板,反而是如同丝绸般顺滑的动画。
|
|
|
|
|
|
|
|
|
|
|
|
👇 在下方的演示中调整播放速度,观察每一帧的位移是如何连缀成流畅运动的:
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
<AnimationLoopDemo />
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
---
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
## 4. 瞎子摸象:在 Canvas 里面怎么做点击交互?
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
因为 Canvas 画布在浏览器眼里只是一张没有任何结构的“颜料布”。假设你在画布上用 `arc()` 画了一只怪兽,当你想要实现“点击怪兽扣血”时,你**根本没法**使用传统的 `document.getElementById` 来获取这个怪兽。因为在 HTML 结构中,只有那个宽 600 像素的死板 `<canvas>` 标签。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
这就是图形编程中最经典的问题:**碰撞检测 (Collision Detection) 与事件代理**。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
由于浏览器只知道你的鼠标点击了 Canvas 的屏幕坐标 `(x, y)`,你需要自己去通过初中的几何数学进行反算:
|
|
|
|
|
|
- **对于圆形:** 通过勾股定理计算 `鼠标点击处` 到 `圆心位置` 的距离,如果距离小于半径,则说明“被点中了”。
|
|
|
|
|
|
- **对于矩形:** 判断点击的 `x` 是否在矩形的左右边界内,同时 `y` 是否在上下边界内。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
无论你的画布上有多少元素,鼠标悬停或点击事件永远是绑定在 Canvas 这个唯一容器上的,这就是终极的“事件委托”。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
👇 试着在下面使用鼠标(点击、拖拽、悬停)或键盘(方向键移动),体会这种“手动算距离”的底层交互逻辑:
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
<EventHandlingDemo />
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
## 5. 解放算力:粒子系统与视觉魔法
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
到了这一步,当我们把“坐标系”、“动画循环”以及“颜色与形状”全部融合,并将其数量暴增到成百上千个微小碎片时,你就掌握了引爆视觉的终极杀气:**粒子系统(Particle System)**。
|
|
|
|
|
|
|
|
|
|
|
|
其核心思路极其粗暴且有效:
|
|
|
|
|
|
1. 建立一个巨大的数组,里面塞满了几百个独立的“粒子对象”。
|
|
|
|
|
|
2. 每个对象拥有自己的独立生命周期(`life`)、加速度(`vx/vy`)、重力阻尼(`gravity`)。
|
|
|
|
|
|
3. 每次 `requestAnimationFrame` 触发时,遍历更新这几百个粒子,然后渲染,最后悄悄清理掉那些“死亡”(生命值耗尽/掉出屏幕)的粒子。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
你的浏览器一瞬间就能变成一台制造烟花、大雪和爆炸的梦工厂。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
👇 点击不同的效果,调整重力与粒子数,观察它们是如何通过最简单的物理数学公式呈现出复杂的群体视觉:
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
<ParticleSystemDemo />
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
---
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-22 01:21:39 +08:00
|
|
|
|
## 6. 守护 FPS 荣耀:如何应对高烧的 CPU?
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
让成千上万个对象在一秒内计算并重画 60 遍是非常消耗性能的。如果毫无章法,你的电脑风扇很快就会起飞。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
以下是真正引擎大佬用来抢救帧率的“护体绝技”:
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
1. **局部擦黑板(脏矩形 Dirty Rect):**
|
|
|
|
|
|
一个角色在宽广的草原上奔跑,你千万不要每帧去 `clearRect` 整片大草原!角色经过哪一小块,你就用“小板擦”擦掉那一块并覆盖重绘,性能立刻飙升指数倍。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
2. **后台替身魔法(离屏 Canvas):**
|
|
|
|
|
|
如果背景是繁星漫天、有着各种复杂绚丽的山脉,每次都实时渲染太蠢了。我们通常在内存里偷偷建一个看不见的 `<canvas>`,把它精美地画上去一次。之后的每一帧刷新中,只需要通过 `drawImage()` 将这张合成好的“静态底片”直接贴出,免去了海量的基础计算。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
3. **批量洗画笔(Batching):**
|
|
|
|
|
|
调色盘里从红色换到蓝色,在底层是昂贵的。如果画布上有 1000 个红色圆和 1000 个蓝色圆交叉散落。最快的方法是:先把红颜料准备好,遍历画完所有红圈,再换蓝颜料画所有蓝圈。这是著名的批量渲染(Batch Rendering)思想。
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
👇 将对象数量拉到 3000 以上,看着网页掉进卡顿的深渊,再依次打开右下方的“优化技术”开关,亲眼见证实打实的帧率抢救:
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
<PerformanceDemo />
|
2026-01-18 12:21:49 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-02-24 08:34:53 +08:00
|
|
|
|
## 7. 专业名词总结
|
|
|
|
|
|
|
|
|
|
|
|
| 术语 | 通俗解释 |
|
|
|
|
|
|
| --- | --- |
|
|
|
|
|
|
| **Canvas** | HTML5 提供的 2D 画布。绘制极快,但画完就变成颜料像素,不支持通过 DOM 操作内容。 |
|
|
|
|
|
|
| **SVG** | 矢量图。放大永远不模糊,且每个图形都是独立的标签元素,可以轻易绑定各种 CSS 样式和交互。 |
|
|
|
|
|
|
| **Context (ctx)** | 你申请到的那支“2D 魔法画笔”,用来调色、设定形状和绘制各种特殊效果。 |
|
|
|
|
|
|
| **requestAnimationFrame** | 浏览器内置的神级节拍器,会严格依照显示器的刷新率执行回调,是制作丝滑动画的不二之选。 |
|
|
|
|
|
|
| **FPS (Frame Rate)** | 帧率。60 FPS 代表一秒内浏览器帮你无缝擦除了 60 次画布并重画了 60 副新图。 |
|
|
|
|
|
|
| **脏矩形 (Dirty Rect)** | 只在发生变化的那一点微小区域内进行精准擦除和重绘,从而强力保留性能。 |
|
|
|
|
|
|
| **离屏 Canvas** | 藏在内存里的“影子画布”。把极度复杂但不会动的景物提前画好,以后就当死贴图拿来重复使用。 |
|
|
|
|
|
|
|
|
|
|
|
|
> 从一条简单的直线段,到宏大绚丽的粒子系统引擎;一切看似魔法的特效,不过是每秒 60 次的坐标计算与重绘轮回罢了。
|