第2章:基础概念与技术选型
2.1 什么是无限画布
无限画布(Infinite Canvas)是一种可以无限延伸、自由缩放的 2D 图形编辑界面。
核心特征:
- 画布范围理论上无限大
- 用户通过「视口」查看画布的一部分
- 支持平滑缩放(0.02x ~ 10x)
- 支持自由平移
典型产品:Figma、Miro、Canva、稿定设计
2.2 传统 DOM 渲染的瓶颈
假设用户在画布上放置 1000 个元素:
传统 DOM 方案:
1. 创建 1000 个 DOM 节点
2. 每次缩放,1000 个节点都要重新计算样式
3. 每次平移,1000 个节点都要重绘
4. 浏览器的渲染管线被严重阻塞性能数据对比:
| 元素数量 | DOM 渲染 FPS | WebGL 渲染 FPS |
|---|---|---|
| 100 | 60 | 60 |
| 500 | 45 | 60 |
| 1000 | 20 | 60 |
| 5000 | 5 | 58 |
| 10000 | 卡死 | 55 |
DOM 的本质问题:每个元素都是独立的渲染单元,无法批量处理。
2.3 为什么选择 WebGL
WebGL 是浏览器中直接操作 GPU 的接口。
核心优势:
- 批量渲染:成百上千个元素可以在一次 GPU 调用中完成
- 硬件加速:矩阵变换由 GPU 并行计算,而非 CPU 串行
- 内存效率:纹理可在 GPU 显存中复用
一句话总结:DOM 是「逐个处理」,WebGL 是「批量处理」。
2.4 渲染引擎选型:Piso(PixiJS 定制版)
项目基于 Piso —— 一个基于 PixiJS 深度定制的 2D WebGL 渲染引擎。
为什么不直接用 PixiJS?
PixiJS 是游戏引擎,而设计编辑器有特殊需求:
| 需求 | PixiJS | Piso |
|---|---|---|
| 高精度矢量 | ❌ | ✅ SkiaCanvas 支持 |
| 文字排版 | 基础 | 专业级 TypeTool |
| Mipmap 控制 | 自动 | 可动态切换 |
| 多渲染器 | 单一 | Canvas/WebGL/Skia |
Piso 模块结构
@piso/core ← 核心:Texture, Matrix, Point, Rectangle
@piso/display ← 显示:Container, DisplayObject
@piso/graphics ← 图形:Graphics(矢量绘制)
@piso/sprite ← 精灵:Sprite(位图渲染)
@piso/app ← 应用:Application, Ticker
@piso/math ← 数学:Matrix, Polygon, 几何计算
@piso/sk-renderer ← Skia 渲染器(高精度)2.5 核心架构:三层结构
无限画布渲染引擎采用三层架构:
┌─────────────────────────────────────────────┐
│ Surface(画布表面) │
│ ───────────────────────────────────────── │
│ 职责:统筹全局,协调各组件 │
└─────────────────────────────────────────────┘
│
├─────────────┬─────────────┬─────────────┐
↓ ↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Viewport │ │ VmEngine │ │ Processor│ │ Plugin │
│ 视口 │ │ 视图引擎 │ │ 处理器 │ │ 插件 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘各层职责
Surface:
- 管理渲染循环(Ticker,16ms/帧)
- 创建和销毁各子系统
- 提供对外 API
Viewport:
- 管理相机位置(position)和缩放(zoom)
- 坐标转换(屏幕坐标 ⇄ 世界坐标)
- 视口边界限制
VmEngine:
- 数据模型 → 渲染对象映射
- 对象池管理
- 增量更新
Processor:
- 处理 Action(add/remove/update/move)
- 驱动 VmEngine 更新
Plugin:
- 扩展能力(参考线、标尺、轮廓等)
2.6 数据流:从数据到像素
用户操作如何最终显示在屏幕上?
1. 用户拖动元素
↓
2. 编辑器框架更新数据模型(Model)
↓
3. 生成 Action:{ type: 'update_elements', ... }
↓
4. Surface.commit(action)
↓
5. Processor 处理 Action
↓
6. VmEngine 更新对应的 Vm
↓
7. Vm.setState() 触发 render()
↓
8. 下一帧 Ticker 执行,GPU 绘制
↓
9. 像素显示在屏幕上关键点:数据变更 → Action → Vm 更新 → 渲染。这是「响应式」设计。
2.7 核心类关系图
Surface
│
┌───────────────┼───────────────┐
│ │ │
Viewport VmEngine EventBoundary
│ │ │
│ ┌────┴────┐ │
│ │ │ │
↓ PageVm ElementVm │
Position │ │ │
Zoom └────┬────┘ │
│ │
↓ │
PixiJS Stage ←───────┘
│
↓
WebGL Context
│
↓
GPU2.8 关键代码入口
Surface 初始化
typescript
// infinite-renderer/src/surfaces/surface.ts
constructor(options: Partial<ISurfaceOptions> = {}) {
// 1. 创建视口
this._viewport = new Viewport();
this._viewport.initialize(new PageVm(), renderOptions);
// 2. 创建上下文
this._context = new Context({
pixelRatio: window.devicePixelRatio,
});
// 3. 创建视图引擎
this.vmEngine = new VmEngine({
context: this.context,
});
// 4. 创建事件边界(碰撞检测)
this.eventBoundary = new EventBoundary({
viewport: this.viewport,
});
// 5. 创建插件系统
this._plugin = new PluginSystem({
viewport: this.viewport,
vmEngine: this.vmEngine,
context: this.context,
eventBoundary: this.eventBoundary,
});
// 6. 启动渲染循环
this._context.ticker.add((t) => {
this.viewport.app.ticker.update();
}, UPDATE_PRIORITY.LOW);
}提交变更
typescript
// 处理数据变更
commit(action: Action): void {
this.processor.process(action); // 处理 Action
this.context.emit('action', action); // 触发事件
}碰撞检测
typescript
// 判断点击命中哪个元素
hitTest(point: IPointData): BaseElementModel | null {
const target = this.eventBoundary.hitTest(point);
return target ? target.getModel() : null;
}2.9 与传统编辑器的本质区别
| 维度 | 传统 DOM 编辑器 | 无限画布编辑器 |
|---|---|---|
| 渲染方式 | 浏览器渲染管线 | WebGL 直接绘制 |
| 坐标系统 | CSS 像素 | 自定义世界坐标 |
| 缩放实现 | transform: scale() | 矩阵变换 |
| 事件处理 | DOM 事件冒泡 | 自定义碰撞检测 |
| 性能瓶颈 | DOM 节点数量 | GPU 填充率 |
| 内存模型 | DOM Tree | 对象池 + 纹理缓存 |
本质区别:无限画布接管了浏览器的渲染职责,自己实现了一套「迷你浏览器」。
2.10 本章小结
- 无限画布:可无限延伸、自由缩放的 2D 编辑界面
- DOM 瓶颈:节点数量增加时性能急剧下降
- WebGL 优势:批量渲染、GPU 加速
- Piso:基于 PixiJS 的定制渲染引擎
- 三层架构:Surface → Viewport/VmEngine/Processor/Plugin
- 数据流:Model → Action → Vm → Render → GPU
下一章:第3章:坐标系统与矩阵变换 —— 理解无限画布的数学基础
