第6章:元素生命周期与渲染
核心问题:一个元素从创建到销毁经历了哪些阶段?
在无限画布中,每个可见的图形元素(图片、文字、形状等)都由对应的 ViewModel 管理其渲染。本章将深入探讨元素的完整生命周期,从数据模型创建到最终渲染销毁的每个阶段,以及各类具体元素的实现细节。
目录
- 6.1 生命周期概述
- 6.2 数据模型生命周期
- 6.3 视图模型生命周期
- 6.4 BaseVm 状态管理机制
- 6.5 BaseElementVm 详解
- 6.6 具体元素实现
- 6.7 特殊元素解析
- 6.8 渲染完成与异步加载
- 6.9 本章小结
6.1 生命周期概述
6.1.1 双重生命周期体系
无限画布中的元素具有双重生命周期:
- 数据模型生命周期(Model Lifecycle):由业务层驱动,响应用户操作
- 视图模型生命周期(ViewModel Lifecycle):由渲染层驱动,响应状态变化
┌─────────────────────────────────────────────────────────────────┐
│ 数据模型生命周期 │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │modelCreated│→│modelUpdated│→│ modelMoved │→│modelRemoved│ │
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ VmEngine │ │
│ │ (触发器 / 协调者) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│ │ shouldUpdate│→│beforeUpdate│→│ render │→│afterUpdate │ │
│ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
│ 视图模型生命周期 │
└─────────────────────────────────────────────────────────────────┘设计意图:
- 解耦数据与渲染:数据变化通知 ViewModel,但不直接控制渲染细节
- 优化渲染性能:ViewModel 可以通过
shouldUpdate跳过不必要的渲染 - 异步资源处理:数据模型生命周期可以触发异步资源加载
6.1.2 生命周期时序图
以创建一个图片元素为例,完整的生命周期时序:
用户操作 VmEngine ImageVm PixiJS
│ │ │ │
│ 添加图片元素 │ │ │
│────────────────►│ │ │
│ │ │ │
│ │ new ImageVm() │ │
│ │───────────────────►│ │
│ │ │ constructor() │
│ │ │ onConstruct() │
│ │ │ │
│ │ element.context │ │
│ │───────────────────►│ │
│ │ │ │
│ │ element.setModel() │ │
│ │───────────────────►│ │
│ │ │ │
│ │ modelCreated() │ │
│ │───────────────────►│ │
│ │ │ updateTransform() │
│ │ │ setState() │
│ │ │ │ │
│ │ │ │ shouldUpdate() │
│ │ │ │ beforeUpdate() │
│ │ │ │ render()───────┼────► 创建 Sprite
│ │ │ │ afterUpdate() │
│ │ │ │
│ │ parent.addChild() │ │
│ │───────────────────►│ │
│ │ │ 添加到渲染树 │
│ │ │────────────────────┼────► Container.addChild()
│ │ │ │
│ │ │ 异步加载图片 │
│ │ │····················│····► fetch image
│ │ │ │
│ │ │ complete() │
│ │ │───────────────────►│ 渲染完成6.2 数据模型生命周期
数据模型生命周期由 IBaseElementLifecycle 接口定义:
代码位置:infinite-renderer/src/types/element-vm.ts
/**
* 数据模型生命周期
*/
export interface IBaseElementLifecycle<P extends BaseElementModel | PageModel = BaseElementModel> {
/**
* 模型初始化
* @param model 数据模型
* @param context 全局上下文
*/
modelCreated?(model: P, context: IContext): void;
/**
* 模型数据更新
* @param model 数据模型
* @param context 全局上下文
*/
modelUpdated?(model: P, context: IContext): void;
/**
* 模型被移动
* @param model 数据模型
* @param parent 新的父元素
* @param index 在父容器中的位置
* @param context 全局上下文
*/
modelMoved?(model: P, parent: ParentModel, index: number, context: IContext): void;
/**
* 模型被移除
* @param model 数据模型
* @param context 全局上下文
*/
modelRemoved?(model: P, context: IContext): void;
}6.2.1 modelCreated - 模型创建
触发时机:VmEngine.buildVm 或 VmEngine.createVm 创建完 ViewModel 后调用。
职责:
- 初始化元素的变换属性(位置、旋转、缩放)
- 设置元素的渲染状态
- 注册必要的资源
典型实现(以 ImageVm 为例):
// ImageVm.modelCreated
modelCreated(model: ImageModel): void {
// 1. 更新状态(会触发 setState → render)
this.updateState(model);
// 2. 设置视图名称(便于调试)
this.view.name = model.type;
}
// updateState 方法
protected updateState(model: ImageModel) {
// 更新变换矩阵
this.updateTransform(model);
// 更新水印图层
this.updateWatermark(model);
// 更新蒙版信息
this.updateMaskInfo(model);
// 更新 AI 水印
this.updateAiWatermark(model);
// 兼容拖拽场景
if (model.$readyForDrop) {
const alpha = model.$insideDropArea ? 0 : 0.3;
this.view.alpha *= alpha;
}
// 检测是否为动图
const isAnimated = checkIsAnimatedImage(model);
// 设置渲染状态
this.setState({
uuid: model.uuid,
url: model.url,
width: model.width,
height: model.height,
naturalWidth: model.naturalWidth,
naturalHeight: model.naturalHeight,
mosaic: simplify(model.mosaic),
resourceType: model.resourceType,
isAnimated,
filter: simplify(model.filter),
// ... 更多属性
});
// 更新碰撞区域
this.hitArea.width = model.width;
this.hitArea.height = model.height;
}关键点:
modelCreated通常只调用一次,在元素首次创建时- 应该完成所有初始化工作,包括设置初始渲染状态
- 可以触发异步资源加载
6.2.2 modelUpdated - 模型更新
触发时机:Processor.processUpdateElements 处理更新 Action 时调用。
职责:
- 响应数据变化,更新渲染状态
- 重新计算变换矩阵
- 触发必要的重新渲染
典型实现:
// ImageVm.modelUpdated
modelUpdated(model: ImageModel): void {
// 复用 updateState 方法
this.updateState(model);
}
// GroupVm.modelUpdated
modelUpdated(model: GroupElementModel): void {
this.updateTransform(model);
this.updateWatermark(model);
this.updateMaskInfo(model);
}
// ShapeVm.modelUpdated
modelUpdated(model: ShapeElementModel): void {
this.updateState(model);
}设计模式:
- 大多数元素的
modelCreated和modelUpdated实现相似 - 通过提取公共的
updateState方法避免代码重复 - 状态驱动:只需更新状态,渲染逻辑由
render方法处理
6.2.3 modelMoved - 模型移动
触发时机:元素在元素树中移动位置时调用。
场景示例:
- 将元素从一个组移动到另一个组
- 调整元素在层级中的顺序
- 拖拽元素到新的父容器
实现示例:
// Processor.processMoveElements
processMoveElements(action: MoveElements) {
const { index, parent, elements } = action;
const parentVm = this.vmEngine.getVm(parent);
for (const [i, element] of elements.entries()) {
const elementVm = this.vmEngine.getVm(element);
let idx = 0;
// 添加到新位置
if (typeof index === 'number') {
parentVm.addChildAt(elementVm, index + i);
idx = index + i;
} else {
parentVm.addChild(elementVm);
idx = parentVm.children.length - 1;
}
// 调用移动生命周期
if (typeof elementVm.modelMoved === 'function') {
elementVm.modelMoved(element, parent, idx, this.context);
}
}
}注意:
- 移动操作会自动从原父容器中移除元素
modelMoved可以用于更新依赖父容器的状态
6.2.4 modelRemoved - 模型移除
触发时机:VmEngine.removeVm 从缓存中移除 ViewModel 前调用。
职责:
- 清理元素持有的资源
- 取消进行中的异步操作
- 移除事件监听
实现示例:
// VmEngine.removeElement
private removeElement<T extends BaseElementModel>(
model: T,
options?: IRemoveOptions | boolean,
): IBaseElementVm<T> | null {
const element = this.getVm(model);
if (!element) {
return null;
}
// ... 处理子元素
// 调用生命周期
if (typeof element.modelRemoved === 'function') {
element.modelRemoved(model, this.context);
}
// 从缓存中删除
this.elementMap.delete(model.uuid);
// 销毁元素
if (destroy) {
element.destroy({
texture: true,
baseTexture: true,
children: removeChildren,
});
}
return element;
}
// ImageVm.destroy
destroy(options?: boolean | IDestroyOptions): void {
// 销毁子视图模型
this.maskVm?.destroy(options);
this.dynamicImage?.destroy(options);
this.aiWatermark?.destroy(options);
// 调用父类销毁
super.destroy(options);
}6.3 视图模型生命周期
视图模型生命周期由 IBaseVmLifecycle 接口定义,控制渲染行为:
代码位置:infinite-renderer/src/types/vm.ts
export interface IBaseVmLifecycle<T> {
/**
* 捕获错误
*/
catch?(error: any): void;
/**
* 是否需要更新(优化用)
*/
shouldUpdate?(nextState: T): boolean;
/**
* Render 之前
*/
beforeUpdate?(nextState: T): void;
/**
* Render 之后
*/
afterUpdate?(prevState: T): void;
/**
* 渲染方法
*/
render?(prevState: T, nextState: T): void;
}6.3.1 shouldUpdate - 更新判断
作用:判断是否需要执行渲染,返回 false 可跳过本次渲染。
典型场景:
- 状态值未发生实际变化
- 某些条件下不需要渲染
实现示例:
// ShapeVm.shouldUpdate
shouldUpdate(nextState: ShapeState): boolean {
// 使用深比较判断状态是否变化
return !fastEqual(this._state, nextState);
}性能优化原理:
// 假设连续三次 setState
element.setState({ width: 100 }); // 状态变化,需要渲染
element.setState({ width: 100 }); // 状态相同,跳过渲染
element.setState({ width: 100 }); // 状态相同,跳过渲染
// shouldUpdate 返回 false 时,render 不会被调用6.3.2 beforeUpdate - 更新前钩子
作用:在状态更新和渲染之前执行,可用于准备工作。
典型场景:
- 缓存旧状态用于比较
- 准备渲染所需的数据
实现示例:
beforeUpdate(nextState: TextState): void {
// 记录上一次的文本状态,用于连接线计算
this._prevStatus = {
width: this._state.width,
height: this._state.height,
writingMode: this._state.element?.writingMode,
};
}6.3.3 render - 渲染方法
作用:执行实际的渲染逻辑,将状态转换为可视图形。
设计原则:
- 纯渲染:只负责根据状态生成图形,不处理业务逻辑
- 幂等性:相同状态应该产生相同的渲染结果
- 增量更新:尽量复用已有的渲染对象
典型实现(ShapeVm):
// ShapeVm.render
render(): void {
const {
shapeType,
width,
height,
fill,
radius,
stroke,
strokeWidth,
strokeAlignment,
strokeStyle,
dashArray,
} = this._state;
// 清空画布
this.graphics.clear();
// 绘制形状
if (shapeType) {
drawShape(this.graphics, {
shapeType,
width,
height,
fill,
radius,
stroke,
strokeWidth,
strokeAlignment,
strokeStyle,
dashArray,
});
} else {
// 绘制占位符
drawNever(this.graphics, {
width,
height,
});
}
}典型实现(ImageVm):
// ImageVm.render
render(prevState: ImageState, nextState: ImageState): void {
const {
uuid,
url,
mask,
width,
height,
naturalWidth,
naturalHeight,
mosaic,
resourceType,
hasEffects,
hasFilters,
imageTransform,
// ... 更多属性
} = nextState;
// 兼容异常数据
if (!(width > 0 && height > 0)) {
this.complete();
return;
}
// 判断是否使用特效图渲染
const isEffected = hasEffects || hasFilters;
if (
!isAnimated &&
isEffected &&
!this.isDissimilarResizing &&
url
) {
// 使用 DynamicImage 渲染特效图
const image = this.useDynamicImage();
image.update(this._model);
image.resize(width, height);
} else {
// 使用 MaskVm 渲染普通图片
const maskVm = this.useMaskVm();
maskVm.setState({
uuid,
url,
mask,
width,
height,
naturalWidth,
naturalHeight,
// ... 更多属性
});
}
}渲染策略:
- 简单元素(如 ShapeVm):直接在 Graphics 上绘制
- 复杂元素(如 ImageVm):委托给专门的子视图模型处理
6.3.4 afterUpdate - 更新后钩子
作用:在渲染完成后执行,可用于清理工作或触发后续操作。
典型场景:
- 加载额外资源
- 触发完成事件
- 更新缓存
实现示例:
// TextVm.afterUpdate
afterUpdate(prevState: TextRenderState): void {
const preUrl = prevState.ninePatch?.url;
const nextUrl = this._state.ninePatch?.url;
// 九宫格背景图变化时加载新图片
if (preUrl !== nextUrl && nextUrl) {
this._ninePatchLoadingPromise = settings.RESOURCE_ADAPTOR.fetchImage(nextUrl).then(
(result) => {
// 并发加载情况下取最后一个
if (nextUrl !== this._state.ninePatch?.url || !result) {
return;
}
this._image = result;
// 强制更新以应用新图片
this.forceUpdate();
},
);
}
}6.4 BaseVm 状态管理机制
6.4.1 状态驱动渲染
BaseVm 采用类似 React 的状态驱动渲染模式:
abstract class BaseVm<T extends object = object> {
// 组件状态
protected abstract _state: T;
// 设置状态,触发更新
setState(state: Partial<T>): void {
this.batchState = {
...this.batchState,
...state,
};
if (!this._isBatchDisabled) {
this.batchUpdate();
} else {
this.forceUpdate();
}
}
// 获取当前状态
getState(): T {
return this._state;
}
}状态更新流程:
setState({ x: 100 })
│
▼
合并到 batchState
│
▼
batchUpdate() ─── 是否禁用批量更新?
│ │
│ 否 │ 是
▼ ▼
延迟到微任务 forceUpdate()
│ │
▼ │
flush() ◄─────────────────┘
│
▼
shouldUpdate(nextState) ─── 返回 false?
│ │
│ 返回 true ▼
▼ 跳过渲染
_internal_update(nextState)
│
├── beforeUpdate(nextState)
├── this._state = nextState
├── render(prevState, nextState)
└── afterUpdate(prevState)6.4.2 批量更新优化
问题:如果每次 setState 都立即渲染,多次连续更新会导致性能问题。
解决方案:批量更新,将多次 setState 合并为一次渲染。
// 合并更新 state
private batchState: Partial<T> | null = null;
// 当前是否正在 batch 更新中
private isBatching = false;
// 当前组件是否需要更新
private isDirty = false;
/**
* 合并更新组件
*/
batchUpdate() {
this.isDirty = true;
if (!this.isBatching) {
this.isBatching = true;
// 延迟到微任务队列
Promise.resolve().then(() => {
this.isBatching = false;
// 若 batch 更新时是 dirty 状态,则驱动更新
if (this.isDirty) {
this.flush();
}
});
}
}批量更新示例:
// 三次 setState 只触发一次 render
element.setState({ x: 100 }); // 标记 dirty,启动 batch
element.setState({ y: 200 }); // 合并状态
element.setState({ z: 300 }); // 合并状态
// 微任务执行时,合并后的状态为 { x: 100, y: 200, z: 300 }
// 只调用一次 render6.4.3 更新流程源码分析
代码位置:infinite-renderer/src/vms/base/base-vm.ts
private _internal_update(nextState: T) {
// 1. 更新前钩子
if (typeof this.beforeUpdate === 'function') {
this.beforeUpdate(nextState);
}
// 2. 递增更新 ID(用于追踪更新次数)
this._updateID++;
// 3. 更新状态
const prevState = this._state;
this._state = nextState;
// 4. 执行渲染
if (typeof this.render === 'function') {
this.render(prevState, nextState);
}
// 5. 清理 batch 状态
this.batchState = null;
this.isDirty = false;
// 6. 更新后钩子
if (typeof this.afterUpdate === 'function') {
this.afterUpdate(prevState);
}
}
/**
* 强制更新组件
*/
forceUpdate() {
// 已销毁的组件不执行更新
if (this.destroyed) {
console.warn('View has been destroyed.');
return;
}
const nextState = {
...this._state,
...this.batchState,
};
try {
this._internal_update(nextState);
} catch (error) {
this.catch(error);
} finally {
this.batchState = null;
this.isDirty = false;
}
}
flush() {
// 已销毁的组件不执行更新
if (this.destroyed) {
console.warn('View has been destroyed.');
return;
}
try {
if (this.batchState) {
const nextState = {
...this._state,
...this.batchState,
};
// 判断是否需要 rerender
const shouldUpdate = this.shouldUpdate
? this.shouldUpdate(nextState)
: true;
if (shouldUpdate) {
this._internal_update(nextState);
}
}
} catch (error) {
this.catch(error);
} finally {
this.batchState = null;
this.isDirty = false;
}
}6.5 BaseElementVm 详解
6.5.1 类继承体系
EventEmitter ← 事件系统
│
▼
BaseVm<T> ← 状态管理
│
▼
BaseContainerVm<T, C> ← 容器管理
│
▼
BaseElementVm<P, T> ← 元素基类
│
├── ImageVm<P extends ImageModel>
├── TextVm<P extends TextModel>
├── ShapeVm<P extends ShapeModel>
├── GroupVm<P extends GroupModel>
└── ... 其他具体元素各层职责:
| 类 | 职责 | 核心能力 |
|---|---|---|
EventEmitter | 事件发布订阅 | emit, on, off |
BaseVm | 状态管理 | setState, render, forceUpdate |
BaseContainerVm | 子元素管理 | addChild, removeChild, 坐标转换 |
BaseElementVm | 元素通用功能 | 变换、包围盒、碰撞检测、图层结构 |
6.5.2 图层结构设计
每个 BaseElementVm 包含三层结构:
代码位置:infinite-renderer/src/vms/base/base-element-vm.ts
constructor() {
super();
// 内容图层:元素自身的渲染内容(Sprite、Graphics 等)
this.contentLayer = this.createContentLayer();
this.contentLayer.name = 'contentLayer';
// 子元素图层:子元素的容器
this.childrenLayer = this.createChildrenLayer();
this.childrenLayer.name = 'childrenLayer';
// 水印图层:元素水印
this.watermarkLayer = new WatermarkVm();
this.watermarkLayer.emitter.on('complete', () => {
if (this._isCompleted) {
this.complete();
}
});
this.watermarkLayer.emitter.on('error', (error) => {
this.catch(error);
});
this.watermarkLayer.view.name = 'watermarkLayer';
// 组装图层顺序(从下到上)
this.view.addChild(
this.contentLayer,
this.childrenLayer,
this.watermarkLayer.view
);
this.onConstruct();
}图层可视化:
┌─────────────────────────────────────────┐
│ view (Container) │
│ │
│ ┌─────────────────────────────────────┐ │
│ │ watermarkLayer (最上层) │ │
│ │ ├── 版权水印 │ │
│ │ └── AI 标识 │ │
│ ├─────────────────────────────────────┤ │
│ │ childrenLayer │ │
│ │ ├── child1 (IBaseElementVm) │ │
│ │ ├── child2 (IBaseElementVm) │ │
│ │ └── ... │ │
│ ├─────────────────────────────────────┤ │
│ │ contentLayer (最下层) │ │
│ │ ├── Sprite (图片) │ │
│ │ ├── Graphics (形状) │ │
│ │ └── TextSprite (文字) │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────┘设计意图:
- 层级分离:内容、子元素、水印各自独立
- 可扩展:子类可以重写
createContentLayer自定义内容图层 - 水印覆盖:水印始终在最上层,不会被子元素遮挡
6.5.3 核心属性与方法
核心属性:
abstract class BaseElementVm<P, T> {
// PixiJS 渲染容器
view: Container<DisplayObject> = new Container();
// 全局上下文
context: IContext | null = null;
// 事件发射器
emitter: EventEmitter<BaseElementVmEvents> = new EventEmitter();
// 父元素引用
parent: IBaseElementVm | IPageVm | null = null;
// 自定义变换矩阵(附加在元素变换之上)
transform: Matrix | null = null;
// 唯一标识
protected _key = uniqueId();
// 数据模型
protected _model!: P;
// 渲染完成状态
protected _isCompleted = true;
}变换更新方法:
/**
* 更新元素 Transform 变换数据
*/
updateTransform(model: P = this._model): void {
// 设置可见性
this.view.visible = !model.hidden && !model.$hidden;
// 分解变换矩阵
decomposeTransform(model, this.transform, this.view.transform);
// 更新透明度
this.updateOpacity(model);
}
/**
* 更新元素透明度
*/
updateOpacity(model: P = this._model): void {
const { opacity = 1 } = model;
this.view.alpha = opacity;
}包围盒计算方法:
// 获取渲染包围盒(世界坐标)
getBounds(skipUpdate?: boolean, newRect?: Rectangle): Rectangle {
return this.view.getBounds(skipUpdate, newRect);
}
// 获取局部包围盒
getLocalBounds(newRect?: Rectangle): Rectangle {
return this.view.getLocalBounds(newRect);
}
// 获取有向包围盒多边形(用于旋转后的精确碰撞检测)
getOrientedPolygon(skipUpdate = false, points?: number[]): number[] {
if (!skipUpdate) {
this.view._recursivePostUpdateTransform();
this.view.updateTransform();
}
const rect = this.getLocalBounds(RECTANGLE);
const matrix = this.view.worldTransform;
const vert = getPoints(rect, tempPoints);
const poly = transform(matrix, vert, points);
return poly;
}
// 获取模型包围盒(基于 model 的 width/height)
getModelBounds(skipUpdate?: boolean, newRect?: Rectangle): Rectangle {
const poly = this.getModelOrientedPolygon(skipUpdate, tempPoints);
return getBounds(poly, newRect);
}
getModelLocalBounds(newRect = new Rectangle()): Rectangle {
const model = this._model;
newRect.x = 0;
newRect.y = 0;
newRect.width = model.width;
newRect.height = model.height;
return newRect;
}碰撞检测方法:
/**
* 检测元素是否包含点
*/
contains(point: IPointData): boolean {
// 优先检测 mask
if (this.view.mask) {
const maskObject = this.getMaskObject();
if (maskObject instanceof Graphics) {
return !!maskObject.containsPoint(point);
}
}
// 检测 hitArea
if (this.view.hitArea) {
const pos = this.getLocalPoint(point, tempPoint);
return !!this.view.hitArea.contains(pos.x, pos.y);
}
return false;
}
/**
* 检测元素是否与矩形碰撞(精确检测)
*/
hits(rect: Rectangle): boolean {
const bounds = this.view.getBounds();
// 快速排除:包围盒不相交
if (!bounds.intersects(rect)) {
return false;
}
// 检测 mask
if (this.view.mask) {
const maskObject = this.getMaskObject();
if (maskObject instanceof Graphics) {
return !!doesGraphicsIntersectRect(maskObject, rect);
}
}
// 检测 hitArea
if (this.view.hitArea && this.view.hitArea instanceof Rectangle) {
return doesHitAreaIntersectRect(this.view.hitArea, rect, this.view.worldTransform);
}
return false;
}
/**
* 检测元素是否与矩形相交(基于包围盒)
*/
intersects(rect: Rectangle): boolean {
const bounds = this.view.getBounds(false, RECTANGLE);
// 包围盒不相交
if (!rect.intersects(bounds)) {
return false;
}
// 完全包含
if (rect.containsRect(bounds)) {
return true;
}
// 精确检测
// ...
}6.6 具体元素实现
6.6.1 ImageVm - 图片元素
代码位置:infinite-renderer/src/vms/image/image-vm.ts
ImageVm 是最复杂的元素之一,支持:
- 普通图片渲染
- 动图播放
- 图片滤镜/特效
- 马赛克
- 蒙版
- AI 水印
状态定义:
export interface ImageState {
uuid?: string;
url?: string;
width: number;
height: number;
naturalWidth: number;
naturalHeight: number;
mosaic?: Mosaic | null;
resourceType?: string;
isAnimated?: boolean;
filter?: Filter | null;
filterInfo?: FilterInfo;
hasFilters?: boolean;
imageEffects?: ImageEffect[];
hasEffects?: boolean;
shadows?: Shadow[];
fillOpacity?: number;
mask?: string;
imageTransform?: Transform;
opacity?: number;
maskPath?: MaskShape;
// ... 更多
}装饰器模式:
ImageVm 使用多个装饰器扩展功能:
@HiddenOnEditing() // 编辑时隐藏
@PrimaryElement() // 主要元素标识
@PreferImage<ImageVm>({ // 优先使用结果图
condition(model) {
// 动图不使用 imageUrl
if (checkIsAnimatedImage(model)) {
return false;
}
// 有特效时使用 imageUrl
return model.hasEffects || model.hasFilters;
},
onImageLoaded() {
this.complete();
// 预热缓存
settings.RESOURCE_ADAPTOR.fetchImage(this._model.url);
},
// ...
})
@EmitError() // 错误事件发射
class ImageVm extends BaseElementVm<ImageModel, ImageState> {
// ...
}渲染策略:
render(prevState: ImageState, nextState: ImageState): void {
const { hasEffects, hasFilters, isAnimated, url } = nextState;
// 图片是否含有特效类属性
const isEffected = hasEffects || hasFilters;
if (
!isAnimated &&
isEffected &&
!this.isDissimilarResizing &&
url
) {
// 使用 DynamicImage 渲染特效图
// DynamicImage 会加载预处理的特效结果图
const image = this.useDynamicImage();
image.update(this._model);
image.resize(width, height);
} else {
// 使用 MaskVm 渲染普通图片
// MaskVm 处理原始图片的加载、蒙版、马赛克等
const maskVm = this.useMaskVm();
maskVm.setState({
uuid,
url,
mask,
width,
height,
// ...
});
}
}变换特殊处理:
override updateTransform(model: ImageModel = this._model): void {
this.view.visible = !model.hidden && !model.$hidden;
// 获取可能被覆盖的属性
const {
left = model.left,
top = model.top,
width = model.width,
height = model.height,
} = model.$override || model;
const { a, b, c, d, tx, ty } = model.transform.localTransform;
const matrix = tempMatrix.set(a, b, c, d, tx, ty);
// 附加额外变换
if (this.transform) {
matrix.prepend(this.transform);
}
// 分解变换到 PixiJS Transform
decomposeLocalTransform(matrix, width, height, this.view.transform);
// 应用偏移
this.view.transform.position.x += left;
this.view.transform.position.y += top;
this.view.alpha = model.opacity;
}6.6.2 TextVm - 文本元素
代码位置:infinite-renderer/src/vms/text/text-vm.ts
TextVm 处理富文本渲染,支持:
- 多样式文本(颜色、字体、大小)
- 文字特效
- 自动换行和排版
- 列表样式
- 九宫格背景
状态定义:
interface TextState extends Partial<TextHolder> {
width: number;
height: number;
textAlign: string;
ninePatch?: NinePatchFilling | null;
$resizing?: boolean;
}
export interface TextRenderState {
width: number;
height: number;
color: string;
effectScale: number;
fontFamily?: string;
fontStyle: string;
fontWeight: number;
fontSize: number;
lineHeight: number;
letterSpacing: number;
textDecoration: string;
writingMode: string;
shadows: BaseShadow[];
textAlign: string;
verticalAlign: string;
contents?: TextContentModel[] | null;
textEffects: TextEffect[];
// ...
}文本排版流程:
protected async updateText(model: T, complete = false) {
const textUpdateID = ++this._textUpdateID;
// 获取排版工具实例
const typetool = await getTypeToolIns();
// 若 updateID 相等,则执行排版
if (textUpdateID === this._textUpdateID) {
// 1. 转换数据模型
const text = this.context?.modelAdaptor?.transformTextModel(model);
// 2. 执行排版
const autoWidth = text.autoAdaptive & 0b10;
const autoHeight = text.autoAdaptive & 0b01;
let layout = typetool.shape(text, {
width: autoWidth ? 0 : text.width,
height: autoHeight ? 0 : text.height,
});
// 3. 获取排版结果
const bbox = layout.bbox();
// 4. 更新模型尺寸(如果变化)
if (!isSame(bbox.width, model.width) || !isSame(bbox.height, model.height)) {
this.context.modelAdaptor.resizeTextModel(model, {
width: bbox.width,
height: bbox.height,
});
}
// 5. 构建渲染数据
const textHolder = this.buildTextHolder(model, text, typetool, layout);
// 6. 设置状态
this.setState(textHolder);
this.forceUpdate();
if (complete) {
this.complete();
}
}
}渲染实现:
render(): void {
const { element, layout, position } = this._state;
const text = this._textSprite;
let { width, height } = this._state;
if (element && layout) {
const { renderRect, ninePatch, $resizing } = this._state;
const hasEffects = element.textEffects?.some(item => item.enable);
const hasHighlight = getHasHighLight(element);
const hasBackground = !!element.background?.enable;
// 更新文字精灵
text.update({
element,
width,
height,
position,
layout,
renderRect,
ninePatch,
image: this._image,
$resizing,
hasBackground,
hasEffects,
hasHighlight,
});
text.dirty();
} else {
text.resize(width, height);
}
// 设置位置
if (position) {
text.x = position.x;
text.y = position.y;
}
}6.6.3 ShapeVm - 形状元素
代码位置:infinite-renderer/src/vms/shape/shape-vm.ts
ShapeVm 是相对简单的元素,主要使用 Graphics 绘制基础图形。
状态定义:
export interface ShapeState {
shapeType?: ShapeType;
fill?: string;
radius?: number;
stroke?: string;
strokeWidth?: number;
strokeAlignment?: ShapeStrokeAlignment;
strokeStyle?: ShapeStrokeStyle | '';
dashArray?: number[];
width: number;
height: number;
}继承关系:
class ShapeVm extends GraphicsElementVm<ShapeElementModel, ShapeState> {
// GraphicsElementVm 提供了 this.graphics 属性
}完整实现:
class ShapeVm extends GraphicsElementVm<ShapeElementModel, ShapeState> {
protected _state: ShapeState = {
width: 0,
height: 0,
};
constructor() {
super();
this.view.name = 'shape';
}
private updateState(model: ShapeElementModel) {
this.updateTransform(model);
this.updateWatermark(model);
this.setState({
shapeType: model.shapeType,
width: model.width,
height: model.height,
radius: model.radius,
fill: model.fill,
stroke: model.stroke,
strokeWidth: model.strokeWidth,
strokeAlignment: model.strokeAlignment || undefined,
strokeStyle: model.strokeStyle,
dashArray: model.dashArray,
});
}
// 自定义锚点计算(三角形特殊处理)
protected buildAnchorPoints(rect: Rectangle, anchors: AnchorPoints): AnchorPoints {
const { shapeType, radius } = this._state;
if (shapeType === 'triangle-up') {
return buildTriangleAnchorPoints(rect, anchors, radius);
}
return super.buildAnchorPoints(rect, anchors);
}
// 碰撞检测:考虑子元素
contains(point: IPointData): boolean {
const bounds = this.view.getBounds();
if (!bounds.contains(point.x, point.y)) {
return false;
}
// 图形区域包含点
if (super.contains(point)) {
return true;
}
// 子元素包含点
for (const child of this.children) {
if (child.contains(point)) {
return true;
}
}
return false;
}
modelCreated(model: ShapeElementModel): void {
this.updateState(model);
}
modelUpdated(model: ShapeElementModel): void {
this.updateState(model);
}
// 优化:状态无变化时跳过渲染
shouldUpdate(nextState: ShapeState): boolean {
return !fastEqual(this._state, nextState);
}
render(): void {
const {
shapeType,
width,
height,
fill,
radius,
stroke,
strokeWidth,
strokeAlignment,
strokeStyle,
dashArray,
} = this._state;
// 清空画布
this.graphics.clear();
if (shapeType) {
// 使用绘制函数绘制形状
drawShape(this.graphics, {
shapeType,
width,
height,
fill,
radius,
stroke,
strokeWidth,
strokeAlignment,
strokeStyle,
dashArray,
});
} else {
// 绘制占位符
drawNever(this.graphics, {
width,
height,
});
}
}
}6.6.4 GroupVm - 组元素
代码位置:infinite-renderer/src/vms/group/group-vm.ts
GroupVm 是最简单的元素之一,主要作为子元素的容器。
完整实现:
class GroupVm
extends BaseGroupElementVm<GroupElementModel>
implements IBaseElementVm<GroupElementModel>
{
protected _state: object = {};
constructor() {
super();
this.view.name = 'group';
}
private updateState(model: GroupElementModel) {
this.updateTransform(model);
this.updateWatermark(model);
this.updateMaskInfo(model);
}
// 使用支持蒙版的容器
protected createChildrenLayer(): Container<DisplayObject> {
return new MaskContainer();
}
// 碰撞检测:基于包围盒
contains(point: IPointData): boolean {
const bounds = this.getBounds();
return bounds.contains(point.x, point.y);
}
hits(rect: Rectangle): boolean {
return this.intersects(rect);
}
intersects(rect: Rectangle): boolean {
const bounds = this.getBounds();
return bounds.intersects(rect);
}
modelCreated(model: GroupElementModel): void {
this.updateState(model);
}
modelUpdated(model: GroupElementModel): void {
this.updateState(model);
}
}设计特点:
- 不需要自己的渲染状态(
_state为空对象) - 不实现
render方法(无需绘制自身内容) - 碰撞检测基于子元素的联合包围盒
- 使用
MaskContainer支持组内蒙版
6.7 特殊元素解析
6.7.1 LayoutVm - 画板元素
代码位置:infinite-renderer/src/vms/layout/layout-vm.ts
LayoutVm 是画板/画布元素,在设计编辑器中代表一个独立的设计区域。
特殊功能:
- 裁剪蒙版(限制子元素渲染范围)
- 背景图/背景色
- 背景遮罩
- AI 水印
- 标题显示
装饰器配置:
@HiddenOnEditing()
@PrimaryLayout((element, model) => {
// 在 plog 模板中作为主画板
return element.context?.templateType === 'plog' && isTopLevelLayoutElement(model);
})
@IgnorePosition((element, model) => {
// 设计模式下忽略顶层画板的位置
return element.context?.stageMode === 'design' && isTopLevelLayoutElement(model);
})
@GlobalBackground((element) => {
// 非白板模式下渲染全局背景
return element.context?.stageMode !== 'board';
})
@EmitError()
class LayoutVm extends BaseElementVm<LayoutModel, LayoutState> {
// ...
}图层结构:
constructor() {
super();
// 创建裁剪蒙版
this.maskData = new MaskData(this.mask);
this.view.mask = this.maskData;
this.view.cullArea = this.hitArea;
this.view.hitArea = this.hitArea;
this.mask.name = 'mask';
this.view.addChild(this.mask);
// 监听背景加载状态
this.layoutBackground.emitter.on('loading', () => this.loading());
this.layoutBackground.emitter.on('complete', () => this.complete());
this.layoutBackground.emitter.on('error', (error) => this.catch(error));
// 图层顺序:背景色 < 背景图 < 遮罩 < 水印
this.contentLayer.addChild(
this.layoutBackground.view,
this.layoutBackgroundMask.view
);
}裁剪蒙版实现:
private updateState(model: LayoutModel) {
// ... 其他更新
let x = 0;
let y = 0;
let { width, height } = model;
// 处理裁剪区域
if (model._clipping?.enable) {
const {
offsetX = x,
offsetY = y,
offsetWidth = width,
offsetHeight = height,
} = model._clipping;
x = offsetX;
y = offsetY;
width = offsetWidth;
height = offsetHeight;
}
// 绘制裁剪蒙版
this.mask.clear();
this.mask.beginFill(0x000000, 1);
this.mask.drawRect(x, y, width, height);
// 更新背景
this.layoutBackground.setState({
x,
y,
width,
height,
background,
// ...
});
}6.7.2 ConnectorVm - 连接线
代码位置:infinite-renderer/src/vms/connector/connector-vm.ts
ConnectorVm 实现元素间的连接线,支持多种线型和箭头样式。
状态定义:
export interface ConnectorState {
points: ConnectorPoint[]; // 路径点
color?: string; // 线条颜色
lineType?: ConnectorLineType; // 线型:直线/折线/曲线
lineStyle?: ConnectorLineStyle; // 样式:实线/虚线
lineWidth?: number; // 线宽
radius?: number; // 圆角半径
startId?: string; // 起点关联元素
startStyle?: JoinStyle; // 起点箭头样式
endId?: string; // 终点关联元素
endStyle?: JoinStyle; // 终点箭头样式
dashArray?: number[]; // 虚线数组
lineCap?: ConnectorLineCap; // 线帽样式
}渲染实现:
render(): void {
const {
points,
color = '#000000',
lineWidth = 4,
radius = 0,
lineType = 'elbowed',
lineStyle = 'solid',
startStyle = 'none',
endStyle = 'none',
dashArray = [],
lineCap = 'butt',
} = this._state;
this.graphics.clear();
this.startView.clear();
this.endView.clear();
// 非法线段
if (points.length <= 1) {
return;
}
// 计算箭头偏移
const startOffset = mapArrowOffset(lineWidth, startStyle);
const endOffset = mapArrowOffset(lineWidth, endStyle);
// 配置线条样式
const lineColor = Color.shared.setValue(color);
const options: IStrokeStyleOptions = {
color: lineColor.toNumber(),
alpha: lineColor.alpha,
width: lineWidth,
};
// 处理虚线
if (lineStyle !== 'solid') {
const totalLength = getLineLength(points, lineType, radius);
const lineDashArray = calculateSymmetricDash(
totalLength - startOffset - endOffset,
dashArray,
2,
);
options.dashArray = lineDashArray;
options.dashOffset = lineDashArray[0] / 2;
}
// 设置线帽样式
switch (lineCap) {
case 'butt':
options.cap = STROKE_CAP.BUTT;
break;
case 'round':
options.cap = STROKE_CAP.ROUND;
break;
case 'square':
options.cap = STROKE_CAP.SQUARE;
break;
}
this.graphics.strokeStyle(options);
// 渲染连线路径
renderLine(this.graphics, points, lineType, radius, startOffset, endOffset);
// 渲染起点箭头
if (startStyle && startStyle !== 'none') {
const start = lineType === 'straight' ? points[points.length - 1] : points[1];
const end = points[0];
this.renderTerminator(
this.startView,
lineWidth,
color,
startStyle,
start,
end,
this.startMatrix.identity(),
);
}
// 渲染终点箭头
if (endStyle && endStyle !== 'none') {
const start = lineType === 'straight' ? points[0] : points[points.length - 2];
const end = points[points.length - 1];
this.renderTerminator(
this.endView,
lineWidth,
color,
endStyle,
start,
end,
this.endMatrix.identity(),
);
}
}箭头绘制:
private renderTerminator(
graphics: Graphics,
width: number,
color: string,
style: JoinStyle,
start: ConnectorPoint,
end: ConnectorPoint,
matrix = Matrix.IDENTITY,
) {
// 计算方向角度
const vx = end.x - start.x;
const vy = end.y - start.y;
const radians = Math.atan2(vy, vx);
const zoom = width / 2;
switch (style) {
case 'solid-arrow':
case 'triangle-arrow':
case 'triangle': {
const item = ArrowMap[style];
const pathData = new SVGPathData(item.path).toAbs();
pathData.scale(zoom, zoom);
// 变换矩阵:移动到箭头位置,旋转到正确方向
matrix
.translate(-item.width * zoom, (-item.height * zoom) / 2)
.rotate(radians)
.translate(end.x, end.y);
graphics.beginFill(color);
graphics.setMatrix(matrix);
graphics.drawPath(Path.SVGDataToPath(pathData));
graphics.endFill();
break;
}
case 'circle': {
const item = ArrowMap.circle;
matrix.rotate(radians).translate(end.x, end.y);
graphics
.beginFill(color)
.setMatrix(matrix)
.drawCircle(0, 0, item.width * 0.5 * zoom)
.endFill();
break;
}
// ... 其他样式
}
}6.8 渲染完成与异步加载
6.8.1 isCompleted 状态管理
许多元素需要加载外部资源(图片、字体等),渲染完成状态用于追踪资源加载进度。
代码位置:infinite-renderer/src/vms/base/base-element-vm.ts
get isCompleted(): boolean {
return (
this._isCompleted &&
// 若水印图层可见,则附加水印完成状态
(!this.watermarkLayer.view.visible || this.watermarkLayer.isCompleted)
);
}
protected _isCompleted = true;ImageVm 的完成状态:
// ImageVm
get isCompleted() {
return super.isCompleted && this._aiWatermarkCompleted;
}
protected _isCompleted = false; // 初始为 false,需要加载图片
protected _aiWatermarkCompleted = true;LayoutVm 的完成状态:
// LayoutVm
get isCompleted(): boolean {
return (
super.isCompleted &&
this.layoutBackground.isCompleted &&
(!this.aiWatermark || this._aiWatermarkCompleted)
);
}6.8.2 loading 与 complete 事件
事件定义:
export interface BaseElementVmEvents {
loading: [];
complete: [];
error: [error: any];
}状态切换方法:
complete(): void {
this._isCompleted = true;
if (this.isCompleted) {
this.emitter.emit('complete');
}
}
loading(): void {
this._isCompleted = false;
this.emitter.emit('loading');
}使用示例:
// ImageVm 图片加载完成
this.maskVm.emitter.on('complete', () => {
this.handleMaskLoaded();
this.complete(); // 触发完成事件
});
// 外部监听
const vm = vmEngine.buildVm(imageModel);
vm.emitter.on('complete', () => {
console.log('图片加载完成');
});6.8.3 级联完成检测
对于复合元素,完成状态需要考虑所有子组件:
// LayoutVm 需要等待背景加载完成
constructor() {
super();
this.layoutBackground.emitter.on('loading', () => this.loading());
this.layoutBackground.emitter.on('complete', () => this.complete());
}
// 完成状态检查所有子组件
get isCompleted(): boolean {
return (
super.isCompleted &&
this.layoutBackground.isCompleted &&
(!this.aiWatermark || this._aiWatermarkCompleted)
);
}级联完成流程:
LayoutVm.isCompleted
│
├── super.isCompleted (BaseElementVm)
│ │
│ ├── this._isCompleted
│ │
│ └── watermarkLayer.isCompleted
│
├── layoutBackground.isCompleted
│ │
│ └── 背景图加载状态
│
└── aiWatermark 加载状态6.9 本章小结
生命周期总结
数据模型生命周期:
| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
modelCreated | 元素创建后 | 初始化状态、加载资源 |
modelUpdated | 属性变化时 | 更新状态、重新渲染 |
modelMoved | 移动到新父容器 | 更新依赖父容器的状态 |
modelRemoved | 从缓存移除前 | 清理资源、取消异步操作 |
视图模型生命周期:
| 钩子 | 触发时机 | 典型用途 |
|---|---|---|
shouldUpdate | setState 后 | 判断是否需要渲染(优化) |
beforeUpdate | render 前 | 准备工作、缓存旧状态 |
render | 状态更新时 | 执行渲染逻辑 |
afterUpdate | render 后 | 加载额外资源、触发事件 |
元素实现模式
简单元素(如 ShapeVm):
- 直接在
render中使用 Graphics 绘制 - 实现
shouldUpdate优化性能
- 直接在
复杂元素(如 ImageVm):
- 委托给专门的子视图模型(MaskVm、DynamicImage)
- 使用装饰器扩展功能
- 处理异步资源加载
容器元素(如 GroupVm):
- 主要作为子元素容器
- 不需要自己的渲染逻辑
- 使用 MaskContainer 支持蒙版
特殊元素(如 LayoutVm、ConnectorVm):
- 复杂的图层结构
- 特殊的变换处理
- 多组件协作
设计原则
- 状态驱动:所有渲染都由状态变化触发
- 批量更新:合并多次 setState 为一次渲染
- 层级分离:内容、子元素、水印各自独立
- 异步友好:完善的加载状态管理
- 可扩展:通过继承和装饰器扩展功能
📖 延伸阅读
📝 练习题
分析题:为什么 ImageVm 的
_isCompleted初始值是false,而 BaseElementVm 是true?实现题:实现一个简单的
CircleVm,要求:- 继承
GraphicsElementVm - 支持
fill、stroke、radius属性 - 实现
shouldUpdate优化
- 继承
设计题:如果需要实现一个"视频元素",它需要支持播放/暂停状态,你会如何设计其生命周期?
下一章:第7章 事件系统与碰撞检测 - 深入了解交互系统的实现
📅 文档更新时间:2026-01-28
👤 维护者:前端团队
