这是一个非常宏大的工程,但也是深入理解图形渲染引擎的最佳途径。为了从零实现一个精简版 MiniRender,我们需要先跳出具体的代码细节,从架构设计和类型定义入手。
这将是我们 Mini-Render 的蓝图。我们将采用 TypeScript 进行设计,利用其强类型特性来规范各模块的接口。
一、 整体目录结构设计
我们将项目分为核心层(Core)、图形层(Graphic)、渲染层(Painter)、工具层(Utils)四个主要部分。
src/
├── core/
│ ├── MiniRender.ts # 入口文件,外观模式,协调各模块
│ ├── Eventful.ts # 事件中心基类 (Observer Pattern)
│ └── env.ts # 环境检测 (Browser, Node, etc.)
├── storage/
│ └── Storage.ts # 图形仓库,管理 DisplayList
├── painter/
│ └── Painter.ts # 渲染器,管理 Canvas 和绘图循环
├── handler/
│ └── Handler.ts # 交互控制器,处理 DOM 事件到图形事件的映射
├── graphic/
│ ├── Element.ts # 所有节点的基类 (变换矩阵、父子关系)
│ ├── Displayable.ts # 可绘制对象的基类 (样式、层级)
│ ├── Group.ts # 组,容器
│ ├── Path.ts # 路径基类 (具体的形状继承此类)
│ ├── style.ts # 样式接口定义
│ └── shape/ # 具体形状实现
│ ├── Circle.ts
│ ├── Rect.ts
│ └── ...
└── utils/
├── matrix.ts # 3x2 仿射变换矩阵运算
├── vector.ts # 向量运算
├── bounding.ts # 包围盒计算
└── guid.ts # 唯一 ID 生成器
二、 核心模块详细设计与类型定义
下面我们将逐个模块定义其 TypeScript 接口和核心类的结构。
1. 基础类型与工具 (Utils & Common)
首先定义最基础的数据结构,这是图形学的基石。
// src/utils/types.ts
// 二维向量/点
export type Point = [number, number];
// 3x2 仿射变换矩阵: [a, b, c, d, tx, ty]
// x' = ax + cy + tx
// y' = bx + dy + ty
export type MatrixArray = [number, number, number, number, number, number];
// 包围盒
export interface BoundingRect {
x: number;
y: number;
width: number;
height: number;
}
// 字典类型
export interface Dictionary<T> {
[key: string]: T;
}
2. 事件系统 (Eventful)
所有的图形元素、MiniRender 实例都需要支持 .on, .off, .trigger。
// src/core/Eventful.ts
export type EventHandler = (...args: any[]) => void;
export interface IEventful {
on(event: string, handler: EventHandler, context?: any): this;
off(event?: string, handler?: EventHandler): this;
trigger(event: string, ...args: any[]): this;
}
// 实现类将作为一个 Mixin 或者基类使用
export class Eventful implements IEventful {
// ... 具体实现省略,标准观察者模式
}
3. 图形层 (Graphic System)
这是最复杂的继承体系。我们需要区分“节点结构”和“渲染属性”。
继承链: Element (变换) -> Displayable (样式) -> Path (几何路径) -> Rect/Circle (具体形状)。
A. 元素基类 (Element) 负责场景图(Scene Graph)的树形结构和坐标变换。
// src/graphic/Element.ts
import { MatrixArray, Point, BoundingRect } from '../utils/types';
import { Eventful } from '../core/Eventful';
export interface ElementProps {
id?: string;
position?: Point; // [x, y]
rotation?: number; // 弧度
scale?: Point; // [sx, sy]
origin?: Point; // 变换中心点 [ox, oy]
}
export abstract class Element extends Eventful {
id: string;
// 变换属性
x: number = 0;
y: number = 0;
rotation: number = 0;
scaleX: number = 1;
scaleY: number = 1;
originX: number = 0;
originY: number = 0;
// 变换矩阵
transform: MatrixArray | null = null; // 局部矩阵
globalTransform: MatrixArray | null = null; // 全局矩阵 (世界坐标)
// 层级关系
parent: Element | null = null;
// 标记是否需要更新矩阵
dirty(): void;
// 计算包围盒 (抽象方法)
abstract getBoundingRect(): BoundingRect;
// 坐标转换
// 将全局坐标转换为局部坐标(用于事件检测)
globalToLocal(x: number, y: number): Point;
}
B. 可绘制对象 (Displayable) 负责具体的渲染样式、绘制顺序 (z-index)。
// src/graphic/Displayable.ts
import { Element } from './Element';
export interface PathStyle {
fill?: string;
stroke?: string;
lineWidth?: number;
opacity?: number;
// ... lineCap, lineJoin, shadowBlur 等
}
export abstract class Displayable extends Element {
// 决定 Canvas 的叠加顺序 (CSS z-index 概念)
zLevel: number = 0;
// 决定同一个 Canvas 内的叠加顺序
z: number = 0;
// 样式
style: PathStyle = {};
// 是否可见
invisible: boolean = false;
// 核心绘制流程
// ctx: CanvasRenderingContext2D
brush(ctx: any): void {
// 1. 保存 context
// 2. 设置样式 (fill, stroke)
// 3. 应用变换 (setTransform)
// 4. 调用具体形状的 buildPath (构建路径)
// 5. 绘制 (fill(), stroke())
// 6. 恢复 context
}
// 抽象方法:构建路径(由子类实现)
abstract buildPath(ctx: any): void;
// 抽象方法:判断点是否在图形内(用于事件拾取)
abstract contain(x: number, y: number): boolean;
}
C. 组 (Group) 容器,不直接绘制,但传递变换。
// src/graphic/Group.ts
import { Element } from './Element';
export class Group extends Element {
readonly isGroup = true;
children: Element[] = [];
add(child: Element): void;
remove(child: Element): void;
removeAll(): void;
// 遍历子元素
traverse(cb: (el: Element) => void): void;
}
4. 仓库层 (Storage)
管理内存中的对象,负责排序。
// src/storage/Storage.ts
import { Element } from '../graphic/Element';
import { Displayable } from '../graphic/Displayable';
export class Storage {
// 根节点列表 (Group 或 Displayable)
private _roots: Element[] = [];
// 扁平化的显示列表 (用于渲染循环)
// 只有 Displayable 会进入此列表,Group 不会
private _displayList: Displayable[] = [];
addRoot(el: Element): void;
delRoot(el: Element): void;
// 获取按 zLevel -> z 排序后的列表
getDisplayList(update?: boolean): Displayable[];
// 标记全部更新
dispose(): void;
}
5. 渲染层 (Painter)
负责操作 DOM 和 Canvas Context。
// src/painter/Painter.ts
import { Storage } from '../storage/Storage';
import { Displayable } from '../graphic/Displayable';
export class Painter {
private _dom: HTMLElement; // 容器 DOM
private _storage: Storage;
// 可能有多个 Canvas 层 (zLevel)
// Map<zLevel, CanvasContext>
private _layers: Map<number, CanvasRenderingContext2D> = new Map();
constructor(dom: HTMLElement, storage: Storage);
// 核心渲染方法
refresh(): void;
// 清除指定层
clear(): void;
// 获取视口宽高
getWidth(): number;
getHeight(): number;
// 内部方法:创建或获取层
private _getLayer(zLevel: number): CanvasRenderingContext2D;
}
6. 交互层 (Handler)
处理鼠标事件,进行图形拾取 (Picking)。
// src/handler/Handler.ts
import { Storage } from '../storage/Storage';
import { Painter } from '../painter/Painter';
import { Element } from '../graphic/Element';
export class Handler {
private _storage: Storage;
private _painter: Painter;
private _dom: HTMLElement;
// 当前鼠标悬停的图形
private _hovered: Element | null = null;
constructor(storage: Storage, painter: Painter, proxyDom: HTMLElement);
// 绑定 DOM 事件监听器
private _initDomEvents(): void;
// 事件分发逻辑
private _dispatch(eventName: string, e: MouseEvent): void;
// 核心:寻找鼠标下方的图形
private _findHover(x: number, y: number): Element | null;
}
7. 入口类 (MiniRender)
整合以上所有模块。
// src/core/MiniRender.ts
import { Storage } from '../storage/Storage';
import { Painter } from '../painter/Painter';
import { Handler } from '../handler/Handler';
import { Element } from '../graphic/Element';
import { Group } from '../graphic/Group';
export interface ZROptions {
renderer?: 'canvas' | 'svg';
devicePixelRatio?: number;
}
export class MiniRender {
id: string;
dom: HTMLElement;
storage: Storage;
painter: Painter;
handler: Handler;
// 动画循环 ID
private _animationId: number;
constructor(dom: HTMLElement, opts?: ZROptions);
// API: 添加元素
add(el: Element): void;
// API: 删除元素
remove(el: Element): void;
// 触发刷新 (通常在下一帧执行)
refresh(): void;
// 销毁实例
dispose(): void;
}
// 工厂函数
export function init(dom: HTMLElement, opts?: ZROptions): MiniRender;
是的,我们的目标就是从零制作一个
mini版本的ZRender,从而学习到ZRender这一著名的库的核心设计内容