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) + HTMLText WebGL 抗锯齿完美 + 高分屏不模糊;HTMLText 是目前唯一能轻松实现富文本局部样式的方案
2. 状态管理 Zustand(或 Jotai + signals) 轻量、响应式、支持中间件(持久化、历史栈)
3. 元素对象缓存 Map 永久缓存(一个元素一个 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 + ColorMatrixFilter PixiJS 原生支持
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),存储所有矩形、圆形的坐标、颜色等。
    • selectedIds: 当前选中的 ID 列表。
    • tool: 当前使用的工具。
  • 特点
    • 单一数据源:画布上显示什么,完全由这里的数据决定。
    • 无副作用:这里的 Action 只修改数据,不直接操作 DOM 或 Canvas。

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

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

  • 职责:将“声明式”的数据(Zustand)转换为“命令式”的 Pixi 调用。
  • 核心机制 - 增量更新 (Diffing)
    • 它维护了一个 spriteMap (Map)。
    • 订阅 (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最轻量的一层,仅负责生命周期管理与组件组装。三层数据驱动架构详情可见文档