源码解读之zrender-ZRender 类(3)

article/2025/9/21 4:59:47

00 小结

当我们在 zrender.init(document.getElementById(“canvas”))时,首先实例化了一个 ZRender 实例,在这个实例化过程中,主要实例化了:

  • Storage 类,作用类似于全局状态管理
  • Painter 类,可以理解为画笔(渲染模式),目前支持svg和canvas模式,默认是canvas
  • Handler 类,先简单理解为用户处理器,它把数据storage和画笔painter的实例都传进去了
  • Animation 类,简单理解为处理图形的位置,形状

在实例化过程中,还遇到了几个文档描述比较模糊但很有用的参数:useDirtyRect,useCoarsePointer,usePointerSize。

image.png

01 代码分析

代码太长,部分省略,只分析主流程

// src/zrender.ts
export interface ZRenderInitOpt {renderer?: string   // 'canvas' or 'svgdevicePixelRatio?: numberwidth?: number | string // 10, 10px, 'auto'height?: number | stringuseDirtyRect?: booleanuseCoarsePointer?: 'auto' | booleanpointerSize?: numberssr?: boolean   // If enable ssr mode.
}class ZRender {dom?: HTMLElement // 如果是ssr-svg渲染,可以不用传domid: numberstorage: Storage        // 数据中心painter: PainterBase    // 视图handler: Handler        // 事件处理animation: Animation    // 动画constructor(id: number, dom?: HTMLElement, opts?: ZRenderInitOpt) {// 初始化储存中心和画布相关属性opts = opts || {};this.dom = dom;this.id = id;const storage = new Storage();let rendererType = opts.renderer || 'canvas';if (!painterCtors[rendererType]) {// 使用第一个注册的渲染器rendererType = zrUtil.keys(painterCtors)[0];}// 如果不是生成环境 抛出错误,👆上面代码做生产兜底if (process.env.NODE_ENV !== 'production') {if (!painterCtors[rendererType]) {throw new Error(`Renderer '${rendererType}' is not imported. Please import it first.`);}}// 这里我们遇到了文档没说明的useDirtyRectopts.useDirtyRect = opts.useDirtyRect == null? false: opts.useDirtyRect;// 画笔实例(svg/canvas)const painter = new painterCtors[rendererType](dom, storage, opts, id);// ssr模式,没有使用过,暂忽略。const ssrMode = opts.ssr || painter.ssrOnly;// 初始化zrender中的storage,painterthis.storage = storage;this.painter = painter;// 如果不是env.node, env.worker, ssrMode,初始化handerProxy。const handerProxy = (!env.node && !env.worker && !ssrMode)? new HandlerProxy(painter.getViewportRoot(), painter.root): null;// useCoarsePointer,5.4.0 版本起支持:是否扩大可点击元素的响应范围。const useCoarsePointer = opts.useCoarsePointer;// 扩大元素响应范围的像素大小,配合 `opts.useCoarsePointer` 使用。const usePointerSize = (useCoarsePointer == null || useCoarsePointer === 'auto')? env.touchEventsSupported: !!useCoarsePointer;const defaultPointerSize = 44;let pointerSize;if (usePointerSize) {// 默认是44,如果传了pointerSize,那就是pointerSizepointerSize = zrUtil.retrieve2(opts.pointerSize, defaultPointerSize);}// 初始化handlerthis.handler = new Handler(storage, painter, handerProxy, painter.root, pointerSize);// 初始化animationthis.animation = new Animation({stage: {update: ssrMode ? null : () => this._flush(true)}});if (!ssrMode) {this.animation.start();}}
}

🤔 03 问题

看源码时遇到几个疑问:

  • if(!painterCtors[rendererType])

这里的 painterCtors 初始值为const painterCtors: Dictionary<PainterBaseCtor> = {},咋一看也没看到它在哪里赋初值,然后在网页打断点调试,发现在初始化的时候会调用 registerPainter

export function registerPainter(name: string, Ctor: PainterBaseCtor) {painterCtors[name] = Ctor;
}// all.ts
import { registerPainter } from './zrender';
import CanvasPainter from './canvas/Painter';
import SVGPainter from './svg/Painter';
registerPainter('canvas', CanvasPainter);
registerPainter('svg', SVGPainter);

这时候已经把 canvas,svg 的 painter 注册在全局变量 painterCtors 上了。

image.png

  • opts.useDirtyRect,这个属性在zrender文档上没找到具体的说明,通过源码可知 opts 传递给了 Painter 类:
// zrender.ts
opts.useDirtyRect = opts.useDirtyRect == null? false: opts.useDirtyRect;const painter = new painterCtors[rendererType](dom, storage, opts, id);

在 CavansPainter 这个类里,useDirtyRect 这个属性主要在 refresh, _doPaintList, _doPaintEl 这几个函数中被使用了,这几个方法主要是控制画布是否更新,根据函数名可以猜测这个属性的功能是"脏矩形渲染",也就是局部渲染,找到图形会变化的区域(脏矩形)做去更新。这个区域外都是不变的。

refresh(paintAll?: boolean) {const list = this.storage.getDisplayList(true);// 省略部分代码...if (this._opts.useDirtyRect) {this._prevDisplayList = list.slice();}return this;
}private _doPaintList(...){// ...const repaintRects = useDirtyRect&& layer.createRepaintRects(list, prevList, this._width, this._height);// ...this._doPaintEl(...);// ...return { finished, needsRefreshHover };
}private _doPaintEl(...) {const ctx = currentLayer.ctx;if (useDirtyRect) {const paintRect = el.getPaintRect();if (!repaintRect || paintRect && paintRect.intersect(repaintRect)) {brush(ctx, el, scope, isLast);el.setPrevPaintRect(paintRect);}}else {brush(ctx, el, scope, isLast);}
}

我做了一个实验来验证这个属性,开启 rendering 中的 paint flashing,中间绿光在闪说明画布在更新:

未开启前,当更新的图形不在区域但还是会更新

iShot_2023-02-06_15.28.36.gif

开启后,更新图形不在区域内不会更新

iShot_2023-02-06_15.36.40.gif

于是我找了这个功能的 commit,在后面的分析中会写到这个 useDirtyRect 的工作流程。

  • useCoarsePointer 和 usePointerSize,主要是 usePointerSize,有些线条比较细,可以使用这个属性加大响应范围。zrender test 实例
// useCoarsePointer,5.4.0 版本起支持:是否扩大可点击元素的响应范围。
const useCoarsePointer = opts.useCoarsePointer;
// 扩大元素响应范围的像素大小,配合 `opts.useCoarsePointer` 使用。
const usePointerSize = (useCoarsePointer == null || useCoarsePointer === 'auto')? env.touchEventsSupported: !!useCoarsePointer;const defaultPointerSize = 44;
let pointerSize;
if (usePointerSize) {// 默认是44,如果传了pointerSize,那就是pointerSizepointerSize = zrUtil.retrieve2(opts.pointerSize, defaultPointerSize);
}
// 初始化handler
this.handler = new Handler(storage, painter, handerProxy, painter.root, pointerSize);// 在Handler.ts里,这个值被用来计算包围盒的大小
const pointerRect = new BoundingRect(x - targetSizeHalf, y - targetSizeHalf, pointerSize, pointerSize);
  • animation.start() 开启动画,利用 requestAnimationFrame。
start() {if (this._running) return;this._time = getTime();this._pausedTime = 0;this._startLoop();
}_startLoop() {const self = this;this._running = true;function step() {if (self._running) {// 递归调用requestAnimationFrame(step);// 没有暂停更新的情况下就执行update!self._paused && self.update();}}// 下次重绘之前调用 step 回调函数更新动画requestAnimationFrame(step);
}

http://chatgpt.dhexx.cn/article/weZfuwj6.shtml

相关文章

【ZRender 渲染引擎 - 壹】 | 基础图形元素绘制

theme: cyanosis 持续创作&#xff0c;加速成长&#xff01;这是我参与「掘金日新计划 10 月更文挑战」的第 7 天&#xff0c;点击查看活动详情 开篇前言 在掘金认识我的都知道&#xff0c;我主要是研究 Flutter 的。其实我一直希望开发出一套好用的 Flutter 的图表库&#xf…

zrender学习

这个是项目总结&#xff0c;不适合学习 <div id"canvas" style"background-image:url(./canvasbg.gif)"></div> 定义zrender初始化对象&#xff0c;背景是一个gif图 样式如下 ↓ 工程里&#xff0c; topo.html <div id "containe…

zrender源码学习笔记(一):认识zrender

本文内容 入门zrender绘制原理 入门zrender zrender是Echarts底层的2D绘图引擎&#xff0c;在搞懂其原理之前&#xff0c;我们先学会如何使用zrender&#xff0c;我们从绘制一个简单圆形入门。这里也给出官网入门教程 初始化 zrender.init(dom)初始化zrender实例&#xff0c…

zrender 知识:使用zrender搭建流程图工具

首先看下最终的效果图&#xff1a; 主要使用的技术是zrender.js和vue.js&#xff0c;zrender 用于实现流程图&#xff0c;vue搭建整体架构。 本篇文章主要面向对zrender有一定了解的同学。 本篇文章只讲解核心flowchart的实现方法。 一.分析 流程图主要包含节点node、联系e…

zrender基础入门,简单的案例图形绘制

一、简单介绍 ZRender是二维绘图引擎&#xff0c;它提供 Canvas、SVG、VML 等多种渲染方式。ZRender也是ECharts的渲染器。 流程图&#xff1a; 二、使用入口 (1)npm install zrender&#xff0c;因为zrender不是浏览器自带不同于前面的canvas与svg&#xff0c;需要先下载 …

二维绘图引擎ZRender

1、开始使用 描述 ZRender是二维绘图引擎&#xff0c;它提供 Canvas、SVG、VML 等多种渲染方式。ZRender 也是 ECharts 的渲染器。 下载 ZRender 项目在 gitHub ,也可以使用 npm install zrender 下载。 在 dist 目录下找到 zrender.js 和 zrender.min.js&#xff0c;前者是开发…

MATLAB中求矩阵的特征值和特征向量

矩阵特征值的数学定义 设A是n阶方阵&#xff0c;如果存在常数λ和n维非零列向量x&#xff0c;使得等式Axλ x成立&#xff0c;则称λ为A的特征值&#xff0c;x是对应特征值λ的特征向量。 求特征值和特征向量&#xff1a; eig(A)&#xff1a;求矩阵A的全部特征值&#xff0c…

简单易懂的特征值与特征向量

特征值与特征向量是线性代数中一个很基础的知识&#xff0c;但是很多人对这两个概念没有一个直观的概念&#xff0c;从直觉上&#xff0c;很难理解这两个东西&#xff0c;只知道公式&#xff0c;但是不知道它代表的意义。当年上现代课的时候&#xff0c;老师根本不会去讲这些东…

特征值和特征向量的几何意义

1. 特征值和特征向量 我们首先回顾下特征值和特征向量的定义如下&#xff1a; A x λ x Ax\lambda x Axλx 其中A是一个 n n n\times n nn的实对称矩阵&#xff0c; x x x是一个n维向量&#xff0c;则我们说 λ \lambda λ是矩阵A的一个特征值&#xff0c;而 x x x是矩阵A的…

特征值和特征向量的通俗解释

我们知道&#xff0c;特征向量的公式是 ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ 其中A代表矩阵&#xff0c;x代表特征向量&#xff0c;代表特征值。 众所周知&#xff0c;特…

对特征值和特征向量的理解

Agenda 1. 特征值和特征向量1.1 特征值和特征向量的通俗解释1.2 如何计算矩阵的特征值和特征向量1.3 特征多项式1.4 特征值和特征向量的性质 1. 特征值和特征向量 在讨论特征值和特征向量之前&#xff0c;必须声明的是现在我们关注的是有限维 线性空间上的线性变换。这里两个关…

线性代数(五)特征值和特征向量

文章目录 一&#xff1a;特征值与特征向量二&#xff1a;特征方程2.1行列式求解的另一种方法--初等变换2.2可逆矩阵定理以及行列式性质的补充2.3特征方程/特征多项式2.4相似性 三&#xff1a;对角化3.1从例子出发3.2定理3.3例子 一&#xff1a;特征值与特征向量 1.定义&#x…

特征值和特征向量(三)

特征值和特征向量&#xff08;三&#xff09; 一、先看一下教科书上的定义&#xff1a;设A是n阶方阵&#xff0c;如果存在常数及非零n向量x&#xff0c;使得&#xff0c;则称是矩阵A的特征值&#xff0c;x是A属于特征值的特征向量。给定n阶矩阵A&#xff0c;行列式 的结果是关…

网页游戏脱机脚本制作视频教程

网页游戏脱机脚本制作视频教程 百度网盘 布脖练馅辰杖怖铱试疤促钩咏合躺酱澈纸罢旨谖谘帘婪尾拾碧鸥居丶骨碳捍饰炔幌干衫乖商衣临衣氛捍运阂妊痰煤籽媒移惶心谑源谑丫松橙湛叭坪佳蛊八婪毒鄙刮碧悠凳炔捍灰钩贺篮媒梅敝粱寡油倨制柿囊谐举婪贫婪奖堂虏啄腊谘剿镜感诺衣挥堂猎…

python可以制作游戏脚本吗_用Python写一个游戏脚本,你会吗?

学习python有一段时间了,由于python语言的强大和简洁,是一个不错的脚本语言,就准备做个游戏脚本练练手。如果你也想多练项目实战。可以去小编的Python交流.裙 :一久武其而而流一思(数字的谐音)转换下可以找到了,里面有最新Python教程项目 听说pywin32写脚本还不错 pyw…

逆水寒商业脚本制作视频

​一章 易语言基础 共6课时 1、关于易语言必须了解的基本知识 2、易语言基本组件(不包括超级列表框)讲解 3、易语言超级列表框详解 3、易语言核心支持库讲解之一 4、易语言核心支持库讲解之二 5、易语言模块制作和DLL制作 6、用制作的模块和DLL开发三个小软件 第二章…

Java开发游戏脚本(第三卷)

游戏脚本开发第三卷 XML文件存储数据使用exe4j打包成exe文件回首BUG最后结语 XML文件存储数据 我举个例子&#xff0c;我的窗口数据需要存储到文件&#xff0c;它的结构为: public class Game {// Game类的成员变量private String Title;private int X;private int Y;private…

乐玩模块脚本实战教程辅助脚本制作开发视频

乐玩插件模块的制作&#xff0c;封装了后台绑定判断&#xff0c;键鼠图色窗口文本输入等游戏辅助常用的方法&#xff0c;每种方法都做了游戏调用测试示范&#xff0c;最后录制了四种多线程方法调用乐玩插件&#xff0c;并通过游戏进行了演示。 学习地址&#xff1a;链接&#x…

游戏多开器制作教程

这里讲解怎么制作多开器&#xff0c;简单易懂的讲解&#xff0c;希望能够记录学习过程。 用易语言来制作自己的小工具&#xff0c;还是挺有成就感的。每个人都可以。 1.常见的几种游戏多开限制-简单说明 2.什么是互斥体 3.互斥体的类型与查找 4.编写多开代码实现游戏多开

C++游戏编程教程(一)

参考书籍&#xff1a;《C游戏编程&#xff1a;创建3D游戏》 注&#xff1a;本教程所有代码的开发环境均为Visual Studio Preview 2022&#xff0c;C标准是C20。 一、初识SDL SDL是一个跨平台的开源多媒体库&#xff0c;被广泛应用于游戏开发&#xff0c;具体可以看这里。另外…