2025-12-17-前端画布设计Vol.1 实现基础元素渲染和状态控制

文章发布时间:

最后更新时间:

页面浏览: 加载中...

PixiJS 实现基础元素渲染和状态控制

PixiJS 是一个强大的 2D 渲染引擎,它使用 WebGL 和 Canvas 技术来高效地渲染图形,主要通过 ElementRenderer 类实现。

基本图形渲染实现

设计的画布中,基本图形是通过 PixiJS 的 Graphics 类绘制的,支持以下基本图形类型:

  1. 矩形 (rect):

    1
    2
    3
    g.rect(0, 0, data.width, data.height);
    g.fill({ color: fillColor, alpha });
    g.stroke({ width: strokeWidth, color: strokeColor });
  2. 圆角矩形 (rounded rectangle):

    1
    2
    3
    g.roundRect(0, 0, data.width, data.height, data.radius);
    g.fill({ color: fillColor, alpha });
    g.stroke({ width: strokeWidth, color: strokeColor });
  3. 圆形 (circle):

    1
    2
    3
    g.ellipse(data.width / 2, data.height / 2, data.width / 2, data.height / 2);
    g.fill({ color: fillColor, alpha });
    g.stroke({ width: strokeWidth, color: strokeColor });
  4. 菱形 (diamond):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    g.poly([
    data.width / 2,
    0,
    data.width,
    data.height / 2,
    data.width / 2,
    data.height,
    0,
    data.height / 2,
    ]);
    g.fill({ color: fillColor, alpha });
    g.stroke({ width: strokeWidth, color: strokeColor });

图形属性实现

每个图形元素都由 CanvasElement 接口定义,支持以下属性:

  1. 背景色 (background):

    • 通过 fill 属性实现
    • 例如:g.fill({ color: fillColor, alpha })
    • 使用 PIXI.Color 类处理颜色值
  2. 边框宽度 (border-width):

    • 通过 strokeWidth 属性实现
    • 例如:g.stroke({ width: strokeWidth, color: strokeColor })
  3. 边框颜色 (border-color):

    • 通过 stroke 属性实现
    • 例如:g.stroke({ width: strokeWidth, color: strokeColor })
    • 同样使用 PIXI.Color 类处理颜色值
  4. 透明度 (alpha):

    • 通过 alpha 属性实现
    • 例如:g.fill({ color: fillColor, alpha })

在 ElementRenderer 类中,图形渲染的过程包括以下步骤:

  1. 首先清空之前的图形绘制:g.clear()
  2. 设置绘制样式(边框宽度、边框颜色、填充颜色、透明度):

    1
    2
    3
    4
    const strokeWidth = data.strokeWidth ?? 2;
    const strokeColor = new PIXI.Color(data.stroke);
    const fillColor = new PIXI.Color(data.fill);
    const alpha = data.alpha ?? 1;
  3. 根据图形类型绘制对应的形状

  4. 设置图形的位置和旋转:

    1
    2
    3
    g.pivot.set(data.width / 2, data.height / 2);
    g.position.set(data.x + data.width / 2, data.y + data.height / 2);
    g.rotation = data.rotation;

特殊功能

  1. 旋转功能: 通过设置 pivot 点和 rotation 属性实现
  2. 圆角矩形: 使用 g.roundRect(x, y, width, height, radius) 方法
  3. 纹理缓存: 对图像元素使用纹理缓存以提高性能
  4. 动态加载: 图像元素支持异步加载纹理

状态管理机制

状态管理由 zustand 库实现,通过 CanvasStore 集中管理所有画布元素的状态。状态管理包含以下几个核心部分:

1. 状态结构

  • elements: 一个记录对象,包含所有画布元素
  • selectedIds: 当前选中的元素 ID 数组
  • tool: 当前使用的工具类型
  • currentStyle: 当前绘制样式(填充色、边框色、边框宽度等)

2. 状态更新机制

状态更新通过以下方法实现:

  • addElement: 添加元素
  • updateElement: 更新元素属性
  • removeElements: 删除元素
  • setSelected: 设置选中的元素
  • batchUpdateElements: 批量更新元素(用于提高性能)

元素渲染机制

元素渲染通过 ElementRenderer 类实现,它与状态管理紧密结合:

1. 状态-渲染同步

在 Core_StageManager.ts 中,有一个关键的订阅机制:

1
2
3
4
useStore.subscribe(   (state) => ({ elements: state.elements, selectedIds: state.selectedIds, tool: state.tool }),   (state) => {     if (!this.state.destroyed) {       this.elementRenderer.renderElements(state.elements, this.elementLayer, this.state.destroyed)       this.transformerRenderer.renderTransformer(         state.elements,         state.selectedIds,         this.elementRenderer.getSpriteMap(),         this.onHandleDown,         this.viewport.scale.x,       )       // ...
//
}
}, { equalityFn: stateEqualityFn }, )

每当状态发生变化时,就会触发渲染更新。

2. 渲染过程

ElementRenderer.renderElements 方法遍历所有元素并执行以下操作:

  1. 元素映射管理:使用 spriteMap 记录已渲染的元素
  2. 类型处理:根据元素类型(矩形、圆形、文本、图像等)进行相应渲染
  3. 属性应用:将状态中的属性(位置、大小、颜色等)应用到渲染对象

3. 状态与渲染的实时同步

当状态变化时,例如:

  • 用户拖动元素时,updateElement 更新元素的 x 和 y 坐标
  • 用户改变填充颜色时,updateElement 更新 fill 属性
  • 选择元素时,setSelected 更新 selectedIds

这些状态变更会立即触发渲染更新,确保 UI 与状态保持同步。

状态控制机制

1. 撤销/重做

项目集成了撤销/重做功能,通过 UndoRedoManager 和命令模式实现:

  • 每个操作(添加、删除、更新)都创建一个命令对象
  • 命令对象包含执行和撤销操作的逻辑
  • undo 和 redo 方法控制历史记录栈

2. 选择状态管理

  • selectedIds 数组跟踪当前选中的元素
  • 选择变化会触发渲染更新,显示选择框和控制点
  • TransformerRenderer 负责渲染选择框和调整手柄

3. 工具状态管理

  • tool 属性跟踪当前使用的工具
  • 工具变化会影响交互行为和光标样式
  • 不同工具对相同的用户输入(如鼠标点击)会有不同的响应

4. 实时协作

项目使用 Yjs 实现实时协作功能:

  • 状态变化通过 Yjs 同步到其他用户
  • Yjs 的 observe 机制确保本地状态与共享状态同步
  • 使用事务(transact)保证操作的原子性

性能优化

  1. 状态比较优化:使用 stateEqualityFn 减少不必要的重渲染
  2. 批量更新:batchUpdateElements 方法用于批量更新元素,减少渲染次数
  3. 精灵映射:ElementRenderer 保留精灵映射以避免重复创建/销毁
  4. 防抖机制:虽然代码中注释掉了防抖,但设计中考虑了性能优化