2025-11-21-canvas项目杂记

1819 个字
9 分钟
2025-11-21-canvas项目杂记

分析#

要做的就是一个类 Canva / Figma 的在线图形绘板,完整需求优先级和覆盖范围如下:

优先级功能模块具体需求是否必须难度
P0基础渲染矩形、圆形、三角形任意填充色、边框色、边框宽度、圆角、透明度Yes★☆
P0图片支持上传 png/jpg/webp,任意缩放、圆角、模糊、灰度、亮度调节、裁剪掩模Yes★★
P0富文本字体、字号、颜色、加粗、斜体、下划线、删除线、文本背景色、文字对齐、行距、局部样式支持Yes★★★★
P0基本交互单选、多选(框选 + Shift)、拖拽、删除、复制粘贴、缩放把手(8 个方向)、旋转把手Yes★★
P0无限画布 + 缩放平移Ctrl+滚轮缩放、空格拖拽平移、无限滚动Yes★☆
P0数据持久化自动 localStorage 保存、打开页面自动恢复Yes★☆
P1高级交互组合(Group)、解散组合、图层排序、辅助对齐线、吸附、旋转任意角度Yes★★★
P1工具栏 & 属性面板顶部工具栏(切换文本/形状/图片模式)、右侧属性面板实时编辑属性Yes★★
P1历史记录Undo / Redo(支持跨会话)Yes★★
P1性能要求100 个复杂元素(图片+富文本)打开 < 3s,拖拽 60fps 不闪烁Yes★★★★
P2未来可扩展实时协同编辑、离线编辑、模板库、导出 PNG/SVG/PDF、激光笔、箭头、自由画笔等No★★★★

架构方案#

  • 渲染层:PixiJS (WebGL) 处理高性能图形渲染 + HTML DOM 处理文本编辑/输入框。
  • 状态管理:Zustand / Pinia (管理庞大的 JSON 画布数据)。
  • 逻辑层:自定义 Class 结构(如  Shape, Tool, History)实现面向对象编程。

二、项目设计要素#

设计维度推荐技术方案
1. 渲染引擎PixiJS v8(WebGL) + HTMLTextWebGL 抗锯齿完美 + 高分屏不模糊;HTMLText 是目前唯一能轻松实现富文本局部样式的方案
2. 状态管理Zustand(或 Jotai + signals)轻量、响应式、支持中间件(持久化、历史栈)
3. 元素对象缓存Map<string, Container> 永久缓存(一个元素一个 Container,永不 destroy)彻底解决闪烁、拖拽中断、光标丢失的根本方案
4. 历史栈Command Pattern + structuredClone 快照(每操作记录 before/after)简单可靠,支持跨页面 Undo
5. 选中/变换系统单独的 SelectionManager + TransformHandles(旋转、缩放把手层也缓存)tldraw/Figma 标配
6. 辅助对齐线拖拽时实时遍历所有元素 bounds,差值 < 5px 就吸附并画蓝线提升专业感
7. 组合(Group)元素加 groupId 字段;选中时绘制大虚线框;拖拽/缩放/旋转时整体应用矩阵变换必须有,属于 P1 核心
8. 数据持久化Zustand middleware persist + localForage(IndexedDB)防止 localStorage 炸掉
9. 图片处理Sprite + Graphics mask(圆角)+ BlurFilter + ColorMatrixFilterPixiJS 原生支持
10. 架构分层- store(纯数据) - rendering(Pixi 元素缓存 & 更新) - interaction(拖拽、选中逻辑) - ui(React 面板)

installed pixi.js@8.14.3 installed zustand@5.0.8 installed nanoid@5.1.6

数据驱动视图”(Data-Driven View)  模式,采用了  React (UI) + Zustand (数据) + PixiJS (渲染)  的三层分离架构

这种架构的核心理念是:PixiJS 实例不保存“业务状态”,它只是 Zustand 数据的“投影”

其中,

  • React 只负责 UI 和事件入口
  • Zustand 是唯一的真实数据源(纯 JSON,可持久化、可协同)
  • PixiJS 层只做“渲染 + 交互计算”,所有对象永久缓存(Map),绝不每帧重建
  • 所有变换(拖拽、缩放、旋转、组合)都在 Pixi 层完成,最后再同步回 Zustand(单向数据流)

三层架构详解#

第一层:数据层 (The Source of Truth) - canvasStore.ts#

这是整个应用的大脑。

  • 职责:只存储纯 JSON 数据(Serializable),不包含任何 UI 实例或 Pixi 对象。
  • 存储内容
    • elements: 一个 Map 对象(Record<ID, Element>),存储所有矩形、圆形的坐标、颜色等。
    • selectedIds: 当前选中的 ID 列表。
    • tool: 当前使用的工具。
  • 特点
    • 单一数据源:画布上显示什么,完全由这里的数据决定。
    • 无副作用:这里的 Action 只修改数据,不直接操作 DOM 或 Canvas。

第二层:适配层 (The Bridge) - StageManager.ts#

这是连接 React/Zustand 和 PixiJS 的胶水层,也是架构中最复杂的部分。

  • 职责:将“声明式”的数据(Zustand)转换为“命令式”的 Pixi 调用。
  • 核心机制 - 增量更新 (Diffing)
    • 它维护了一个  spriteMap (Map<ID, PIXI.Graphics>)。
    • 订阅 (Subscribe):它监听 Store 的变化。
    • 同步 (Sync/Render Loop)
      • Create: Store 有 ID,Map 里没有 -> new PIXI.Graphics()。
      • Update: Store 有,Map 里也有 -> 更新  x, y, width, color。
      • Delete: Store 没有,Map 里有 -> destroy()。
  • 事件转换
    • 它监听 Pixi 的  pointerdown/move/up  事件,将屏幕坐标转换为逻辑坐标,然后调用 Store 的 Action。

第三层:视图层 (The Container) - Canvas.tsx#

这是 React 组件层。

  • 职责
    • 提供  div  容器供 Pixi 挂载。
    • 渲染 HTML UI(工具栏、属性面板)。
    • 生命周期管理:组件 Mount 时初始化  StageManager,Unmount 时销毁。

---

3. 关键数据流转 (Data Flow)#

让我们以  “拖拽矩形移动”  为例,看数据如何在架构中流转:

Input (输入):

  • 用户在画布上按住矩形并移动鼠标。
  • StageManager  的  onPointerMove  被触发。

Logic (逻辑处理):

  • StageManager  计算鼠标的偏移量  (dx, dy)。
  • 不直接修改 Pixi 图形的  graphics.x (这是关键!)。
  • 它调用  store.updateElement(id, { x: newX, y: newY })。

State Update (状态更新):

  • Zustand Store 更新内部的 JSON 数据。
  • Zustand 触发订阅回调 (subscribe)。

Render Sync (渲染同步):

  • StageManager  的  render  方法被调用。
  • 它从  spriteMap  找到对应的 Pixi 实例。
  • 执行  graphic.position.set(newX, newY)。
  • PixiJS 在下一个  requestAnimationFrame  自动重绘 Canvas。

---

4. 为什么选择这种架构?#

优点:#

解耦 (Decoupling):渲染引擎可以随时替换(比如换成 Konva 或原生 Canvas),只需要重写  StageManager,数据层和 UI 层不需要动。

协同编辑 (Collaboration) 友好

  • 如果要实现多入协同,只需要监听 WebSocket 消息,然后更新 Zustand Store。StageManager  会自动把队友的操作画出来,无需写额外的同步绘图逻辑。 撤销/重做 (Undo/Redo) 容易
  • 因为所有状态都在 Store 里,只需要保存/恢复 Store 的快照(或 Patch)即可。 序列化/反序列化
  • 保存项目只需  JSON.stringify(store.elements)。

潜在挑战(及优化方案):#

性能瓶颈

  • 问题:高频触发  Store Update -> Diff  循环可能在元素极多时(>2000 个)产生开销。
  • 优化:对于拖拽这种 60FPS 的操作,可以引入“临时层” (Transient State)。即拖拽时直接修改 Pixi 对象,鼠标松开时再同步到 Store。 复杂性
  • 相比直接用 Canvas API 画图,这种架构代码量更大,需要维护 ID 映射和 Diff 逻辑。

feat(canvas): 重构画布实现,应项目要求,删除了基于tldraw的实现转而选择PixiJS 库重构以进行渲染操作,基于Zustand 进行状态管理。新增的文件中canvasStore.ts主要负责维护整个画布项目的全局可序列化状态,是渲染画布系统中唯一数据来源。Pixi_stageManager.ts 负责将声明式数据(Zustand)实时、高性能地映射为命令式渲染实例(PixiJS),并处理所有用户交互的计算与反馈。canvas下的index.ts最轻量的一层,仅负责生命周期管理与组件组装。三层数据驱动架构详情可见文档

分享到社交平台

将本文分享给你的朋友们

2025-11-21-canvas项目杂记
https://firefly.cuteleaf.cn/posts/2025-11-21-canvas项目杂记/
作者
Zhongye
发布于
2025-11-21
版权声明
CC BY-NC-SA 4.0

评论

Profile Image of the Author
Zhongye
南漂中
公告
新的博客站!旧站点传送门 zhongye1.github.io/Arknight-notes
音乐
专辑封面

音乐

暂无播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章数
142
分类数
14
标签数
214
总字数
339,690
运行天数
0
最后更新
0 天前

目录