ZRender 核心接口解析4:PainterBase——渲染器的统一抽象规范
PainterBase 是 ZRender 中所有渲染器(如 Canvas 渲染器、SVG 渲染器、SSR 渲染器)的抽象基接口,定义了渲染器必须实现的核心方法与属性,确保不同渲染器(客户端/服务端)能提供统一的对外接口,实现“渲染器解耦与可替换”。
无论是客户端的 Canvas/SVG 渲染,还是服务端的静态字符串生成(如 SVG 字符串),都需遵循 PainterBase 规范,让上层模块(如 ZRender 实例、Storage)无需关心具体渲染技术,只需调用接口方法即可完成渲染控制。
一、接口定位与核心作用
1.1 核心定位
PainterBase 的本质是渲染器的“契约”,其核心作用可概括为三点:
- 统一接口规范:定义渲染器的通用能力(如尺寸调整、重绘、资源释放),确保不同渲染器行为一致;
- 解耦渲染实现:上层模块(ZRender/Storage)通过 PainterBase 接口调用渲染逻辑,无需依赖具体渲染器(如 Canvas/SVG),实现“替换渲染器不影响上层代码”;
- 支持多场景:同时覆盖客户端渲染(Canvas/SVG)和服务端渲染(SSR),通过接口方法的“可选实现”适配不同环境(如 SSR 需实现
renderToString
,客户端需实现refreshHover
)。
1.2 接口与实现的关系
PainterBase 是“抽象接口”,无具体实现;实际渲染器需继承并实现该接口的所有必选方法,可选方法(如 renderToString
)仅在特定场景(SSR)下实现。常见的实现类包括:
CanvasPainter
:客户端 Canvas 渲染器(默认渲染器);SVGPainter
:客户端 SVG 渲染器(支持矢量缩放、DOM 级事件);SVGSSRPainter
:服务端 SVG 渲染器(生成静态 SVG 字符串,无 DOM 依赖)。
二、接口属性解析
PainterBase 定义了 3 个核心属性,用于标识渲染器的基本信息与运行环境:
属性名 | 类型 | 必选/可选 | 核心作用 |
---|---|---|---|
type | string | 必选 | 渲染器类型标识(如 'canvas' /'svg' /'svg-ssr' ),用于上层区分渲染器类型。 |
root | `HTMLElement | undefined` | 可选 |
ssrOnly | boolean | 可选 | 是否仅支持 SSR 模式(如 SVGSSRPainter 需设为 true ),客户端渲染器默认 false 。 |
三、接口方法解析
PainterBase 的方法按“功能维度”可分为 6 类:尺寸控制、渲染执行、资源释放、交互支撑、图层配置、样式设置。以下按类别详细解析每个方法的功能、参数与使用场景。
3.1 尺寸控制:管理渲染区域大小
1. resize(width?: number | string, height?: number | string): void
- 功能:调整渲染区域的宽高(如窗口 resize 时同步画布尺寸);
- 参数:
width
/height
:支持数值(如400
,单位 px)、字符串(如'100%'
/'auto'
,自适应父容器);
- 调用时机:
- ZRender 实例调用
resize()
时触发(如用户调整浏览器窗口大小); - 初始化后需修改渲染区域尺寸时(如切换组件布局);
- ZRender 实例调用
- 实现逻辑:
- 客户端渲染器(Canvas/SVG):同步修改 Canvas/SVG 元素的
width
/height
及样式尺寸,避免拉伸变形; - SSR 渲染器:更新内部尺寸记录,后续生成字符串时使用新尺寸。
- 客户端渲染器(Canvas/SVG):同步修改 Canvas/SVG 元素的
2. getWidth(): number
- 功能:获取当前渲染区域的实际宽度(单位 px);
- 返回值:数值型宽高(即使输入为
'100%'
,也返回计算后的实际像素值); - 使用场景:
- ZRender 实例获取画布宽度(如计算元素位置时);
- 上层组件(如 ECharts)判断渲染区域是否合法。
3. getHeight(): number
- 功能:获取当前渲染区域的实际高度(单位 px);
- 使用场景:与
getWidth()
类似,用于尺寸相关的计算(如元素居中、范围检测)。
3.2 渲染执行:触发绘制与静态生成
1. refresh(): void
- 功能:触发全量重绘(将 Storage 中的
_displayList
渲染到画布/生成静态内容); - 调用时机:
- ZRender 实例调用
refresh()
/refreshImmediately()
时; - 元素状态变化(如位置、样式修改)后,标记脏状态并触发重绘;
- ZRender 实例调用
- 实现逻辑:
- 客户端 Canvas 渲染器:清空画布,遍历
_displayList
逐个绘制元素(路径、文本、图片等); - 客户端 SVG 渲染器:更新 SVG 元素的属性(如
d
/fill
/transform
),新增/删除元素节点; - SSR 渲染器:无实际意义(SSR 仅在调用
renderToString
时生成内容)。
- 客户端 Canvas 渲染器:清空画布,遍历
2. clear(): void
- 功能:清空渲染内容(如清空画布、删除所有 SVG 元素);
- 调用时机:
- ZRender 实例调用
clear()
时; - 销毁渲染器前清理资源;
- ZRender 实例调用
- 实现逻辑:
- Canvas 渲染器:调用
ctx.clearRect(0, 0, width, height)
清空画布; - SVG 渲染器:删除 SVG 根元素下的所有子节点;
- SSR 渲染器:重置内部内容缓存。
- Canvas 渲染器:调用
3. renderToString?(): string
- 功能:(SSR 场景可选)生成静态渲染字符串(如 SVG 字符串),用于服务端输出;
- 返回值:渲染内容的字符串形式(如
<svg width="400" height="300">...</svg>
); - 使用场景:
- 服务端渲染(如 Node.js 环境生成静态可视化图表,避免客户端首屏白屏);
- 导出静态内容(如将图表导出为 SVG 字符串保存);
- 注意:客户端渲染器(Canvas/SVG)无需实现此方法,仅 SSR 渲染器需实现。
3.3 资源释放:销毁渲染器与清理内存
dispose(): void
- 功能:销毁渲染器,释放所有资源(避免内存泄漏);
- 调用时机:
- ZRender 实例调用
dispose()
时; - 页面卸载、组件销毁时;
- ZRender 实例调用
- 实现逻辑:
- 客户端渲染器:
- 移除 Canvas/SVG 元素从 DOM 树;
- 清空画布/删除 SVG 节点;
- 释放上下文资源(如 Canvas 2D 上下文、事件监听);
- SSR 渲染器:清空内部缓存、释放尺寸记录等临时数据。
- 客户端渲染器:
3.4 交互支撑:适配鼠标/触摸交互
1. refreshHover(): void
- 功能:单独刷新 hover 状态的元素(优化性能,避免全量重绘);
- 调用时机:
- 鼠标移动时,检测到 hover 元素变化;
- ZRender 实例调用
refreshHover()
/refreshHoverImmediately()
时;
- 实现逻辑:
- 客户端 Canvas 渲染器:仅重绘 hover 相关的元素(如高亮态、提示框),而非全量画布;
- SVG 渲染器:更新 hover 元素的样式(如
fill
/stroke
),无需重绘其他元素; - SSR 渲染器:无意义(SSR 无实时交互),无需实现。
2. getViewportRoot(): HTMLElement
- 功能:获取“视口根元素”(用于事件代理、鼠标坐标计算的基准元素);
- 返回值:DOM 元素(如 Canvas 元素的父容器、SVG 根元素);
- 使用场景:
- Handler 模块绑定鼠标/触摸事件时,将事件代理到视口根元素;
- 计算鼠标相对于画布的坐标时,以视口根元素为基准。
3. getViewportRootOffset(): { offsetLeft: number; offsetTop: number }
- 功能:获取视口根元素相对于文档的偏移量(用于修正鼠标坐标);
- 返回值:包含
offsetLeft
(水平偏移)和offsetTop
(垂直偏移)的对象; - 使用场景:
- 鼠标事件触发时,将文档坐标(
clientX
/clientY
)转换为画布内的相对坐标; - 示例:画布内坐标 = 文档坐标 - 视口根元素偏移量。
- 鼠标事件触发时,将文档坐标(
3.5 图层配置:管理 zlevel 分层渲染
configLayer(zlevel: number, config: Dictionary<any>): void
- 功能:配置指定
zlevel
图层的属性(如透明度、混合模式); - 参数:
zlevel
:图层级别(ZRender 支持分层渲染,zlevel
越高,图层越靠上);config
:图层配置(如{ opacity: 0.5, blend: 'multiply' }
,透明度、颜色混合模式);
- 调用时机:
- 初始化时配置图层属性(如背景层设为半透明);
- 动态修改图层样式(如切换主题时调整图层混合模式);
- 实现逻辑:
- Canvas 渲染器:为对应
zlevel
的 Canvas 层设置样式(如style.opacity
、globalCompositeOperation
); - SVG 渲染器:为对应
zlevel
的 SVG 分组(<g>
)设置属性(如opacity
、mix-blend-mode
)。
- Canvas 渲染器:为对应
3.6 样式设置:控制渲染区域背景
setBackgroundColor(backgroundColor: string | GradientObject | PatternObject): void
- 功能:设置渲染区域的背景色(支持纯色、渐变、图案);
- 参数:
backgroundColor
:- 纯色:字符串(如
'#fff'
/'rgba(255,255,255,0.8)'
); - 渐变:
GradientObject
(如{ type: 'linear', colorStops: [{ offset: 0, color: 'red' }, { offset: 1, color: 'blue' }] }
); - 图案:
PatternObject
(如{ image: img, repeat: 'repeat' }
,基于图片的重复图案);
- 纯色:字符串(如
- 调用时机:
- ZRender 实例调用
setBackgroundColor()
时; - 初始化渲染器时设置背景;
- ZRender 实例调用
- 实现逻辑:
- Canvas 渲染器:
- 清空画布后,先绘制背景(纯色/渐变/图案);
- 渐变需创建
createLinearGradient
/createRadialGradient
,图案需创建createPattern
;
- SVG 渲染器:在 SVG 根元素下添加背景元素(如
<rect>
填充纯色/渐变/图案); - SSR 渲染器:将背景配置写入静态字符串(如 SVG 的
<rect>
节点)。
- Canvas 渲染器:
3.7 渲染器类型标识
getType(): string
- 功能:获取渲染器类型(与
type
属性一致,提供方法式访问); - 返回值:渲染器类型字符串(如
'canvas'
/'svg'
); - 使用场景:
- 上层模块判断当前渲染器类型(如 Painter 需根据类型决定是否支持
refreshHover
); - 调试时打印渲染器信息。
- 上层模块判断当前渲染器类型(如 Painter 需根据类型决定是否支持
四、接口设计意图与核心价值
PainterBase 接口的设计并非单纯“罗列方法”,而是基于 ZRender 的核心需求(解耦、多场景、性能)进行的抽象,其核心价值体现在以下三点:
4.1 解耦渲染实现与上层逻辑
通过统一接口,ZRender 实例、Storage 等上层模块无需关心具体渲染技术:
- 调用
painter.refresh()
时,无需判断是 Canvas 还是 SVG,只需知道“调用后会重绘”; - 替换渲染器时(如从 Canvas 切换到 SVG),上层代码无需修改,只需注册新渲染器(通过
registerPainter
)。
例如,ZRender 实例初始化时选择渲染器的逻辑:
// 无论选择 Canvas 还是 SVG,都通过 PainterBase 接口调用方法
const painter = new painterCtors[rendererType](dom, storage, opts, id);
painter.resize(width, height); // 统一调用 resize 方法
painter.refresh(); // 统一调用 refresh 方法
4.2 支持多场景(客户端/SSR)的灵活适配
通过“可选方法”(如 renderToString
)和“环境判断”(如 ssrOnly
),PainterBase 同时覆盖客户端和服务端场景:
- 客户端渲染器:必须实现
refreshHover
/getViewportRoot
等交互相关方法; - SSR 渲染器:必须实现
renderToString
,无需实现交互相关方法(标记ssrOnly: true
)。
这种设计避免了为不同场景设计多个接口,减少了代码冗余。
4.3 支撑性能优化策略
接口中的部分方法(如 refreshHover
/configLayer
)是性能优化的关键:
refreshHover
:单独刷新 hover 元素,避免全量重绘(尤其在复杂图表中,全量重绘耗时较高);configLayer
:分层渲染可将不同层级的元素隔离,修改某一层时仅重绘该层(如背景层、数据层、tooltip 层)。
五、实际实现案例:CanvasPainter 与 SVGPainter 的差异
以两个常见渲染器为例,说明它们如何实现 PainterBase 接口,以及接口如何兼容不同实现:
方法 | CanvasPainter 实现逻辑 | SVGPainter 实现逻辑 |
---|---|---|
refresh() | 清空 Canvas 上下文,遍历 _displayList 调用 draw 方法绘制每个元素。 | 遍历 _displayList ,更新 SVG 元素的属性(如 d /transform ),新增/删除节点。 |
refreshHover() | 仅重绘 hover 元素(通过缓存 hover 元素列表,避免全量遍历)。 | 直接修改 hover 元素的 fill /stroke 属性,无需重绘其他元素。 |
setBackgroundColor | 用 ctx.fillStyle 设置背景(纯色/渐变/图案),调用 ctx.fillRect 填充整个画布。 | 在 SVG 根元素下添加 <rect> 节点,设置 fill 为背景(纯色/渐变/图案)。 |
renderToString | 不实现(客户端渲染无需生成静态字符串)。 | 不实现(客户端 SVG 已是 DOM 节点,无需字符串生成)。 |
六、总结
PainterBase 是 ZRender 渲染体系的“骨架”,其核心意义在于:
- 定义规范:为所有渲染器提供统一的行为标准,确保上层逻辑的一致性;
- 支撑扩展:新渲染器(如 WebGL 渲染器)只需实现接口方法,即可接入 ZRender 体系;
- 兼容多场景:同时覆盖客户端交互渲染和服务端静态生成,满足不同业务需求。
理解 PainterBase 接口,不仅能掌握 ZRender 渲染器的设计逻辑,也能为自定义渲染器(如特殊场景下的定制化渲染)提供方向——只需遵循接口规范,即可无缝融入 ZRender 生态。