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

article/2025/9/21 5:00:08

本文内容

    • 入门zrender
    • 绘制原理

入门zrender

zrender是Echarts底层的2D绘图引擎,在搞懂其原理之前,我们先学会如何使用zrender,我们从绘制一个简单圆形入门。这里也给出官网入门教程

初始化

zrender.init(dom)初始化zrender实例,入参是DOM容器

var zr = zrender.init(document.getElementById('container'));

创建Circle元素

通过new zrender.Circle(opts)创建Circle元素,入参用来配置circle元素的属性

var circle = new zrender.Circle({shape: {cx: 150,    // 圆心x坐标cy: 50,     // 圆心y坐标 r: 40       // 圆半径},style: {fill: 'none',     // 是否填充stroke: '#000'    // 线条颜色 }});

将元素添加到zrender实例中

zr.add(circle);

将元素添加到zrender实例中,圆形便在页面中呈现。

zrender的使用方法很简单,最终效果:

在这里插入图片描述

绘制原理

我们可以在这里获取到zrender项目源码,在了解具体的绘制原理前,我们先了解zrender的整体结构:

zrender采用MVC结构,M(Model)数据层,用来管理图形数据的增删改查;V(View)视图层,主要负责图形渲染;C(Controller)控制层,主要实现事件交互。

以下是zrender的主要文件:

.
├── Element.ts                    // 各种图形的基类
├── Handler.ts                    // Controller,控制层
├── PainterBase.ts                // 
├── Storage.ts                    // Model数据层
├── all.ts
├── animation                     // 动画相关
├── canvas                        // View视图层相关,主要负责绘制渲染图形
│   ├── Layer.ts
│   ├── Painter.ts
│   ├── canvas.ts
│   ├── dashStyle.ts
│   ├── graphic.ts
│   └── helper.ts
├── config.ts                    // 配置文件
├── contain                      // 包含判断
│   ├── arc.ts
│   ├── cubic.ts
│   ├── line.ts
│   ├── path.ts
│   ├── polygon.ts
│   ├── quadratic.ts
│   ├── text.ts
│   ├── util.ts
│   └── windingLine.ts
├── core                        // 核心与工具代码
│   ├── BoundingRect.ts
│   ├── Eventful.ts
│   ├── GestureMgr.ts
│   ├── LRU.ts
│   ├── OrientedBoundingRect.ts
│   ├── PathProxy.ts           // 绘制相关
│   ├── Point.ts
│   ├── Transformable.ts
│   ├── WeakMap.ts
│   ├── arrayDiff.ts
│   ├── bbox.ts
│   ├── curve.ts
│   ├── dom.ts
│   ├── env.ts
│   ├── event.ts                // 事件
│   ├── fourPointsTransform.ts
│   ├── matrix.ts
│   ├── platform.ts
│   ├── timsort.ts
│   ├── types.ts
│   ├── util.ts                // utils.guid() 生成唯一ID
│   └── vector.ts
├── debug
│   └── showDebugDirtyRect.ts
├── dom
│   └── HandlerProxy.ts        // 与dom事件有关
├── export.ts
├── global.d.ts
├── graphic                    // 图形相关
│   ├── CompoundPath.ts
│   ├── Displayable.ts         // 图形的基类
│   ├── Gradient.ts
│   ├── Group.ts
│   ├── Image.ts
│   ├── IncrementalDisplayable.ts
│   ├── LinearGradient.ts
│   ├── Path.ts
│   ├── Pattern.ts
│   ├── RadialGradient.ts
│   ├── TSpan.ts
│   ├── Text.ts
│   ├── constants.ts
│   ├── helper
│   └── shape            // 定义了各种图形
│       ├── Arc.ts
│       ├── BezierCurve.ts
│       ├── Circle.ts
│       ├── Droplet.ts
│       ├── Ellipse.ts
│       ├── Heart.ts
│       ├── Isogon.ts
│       ├── Line.ts
│       ├── Polygon.ts
│       ├── Polyline.ts
│       ├── Rect.ts
│       ├── Ring.ts
│       ├── Rose.ts
│       ├── Sector.ts
│       ├── Star.ts
│       └── Trochoid.ts
├── mixin
├── svg
├── svg-legacy
├── tool
└── zrender.ts            // 入口

接下来我们研究zrender初始化时做了什么事情:

/*** Initializing a zrender instance** @param dom Not necessary if using SSR painter like svg-ssr*/
export function init(dom?: HTMLElement | null, opts?: ZRenderInitOpt) {// 调用构造函数(id, dom, opts) const zr = new ZRender(zrUtil.guid(), dom, opts);// instances数组用来存放zrender实例instances[zr.id] = zr;return zr;
}

通过源码可见init函数接收dom和opt参数,通过ZRender构造方法创建zrender实例,然后将实例存储在数组中。初始化参数opt有如下选项(都是可选的)

名称类型默认值描述
rendererstring'canvas'渲染方式,支持:'canavs''svg''vml'
devicePixelRationumber2画布大小与容器大小之比,仅当 renderer'canvas' 时有效。
widthnumber|string'auto'画布宽度,设为 'auto' 则根据 devicePixelRatio 与容器宽度自动计算。
heightnumber|string'auto'画布高度,设为 'auto' 则根据 devicePixelRatio 与容器高度自动计算。
useCoarsePointer'auto'|boolean'auto'(5.4.0 版本起支持)是否扩大可点击元素的响应范围。'auto' 表示对移动设备开启;true 表示总是开启;false 表示总是不开启。
pointerSizenumber44扩大元素响应范围的像素大小,配合 opts.useCoarsePointer 使用。
useDirtyRecboolean'false'
ssrboolean'false'是否支持ssr模式

zrUtil.guid()用来创建实例的唯一ID:

let idStart = 0x0907;export function guid(): number {return idStart++;
}

ZRender的构造函数如下:

    constructor(id: number, dom?: HTMLElement, opts?: ZRenderInitOpt) {opts = opts || {};/*** @type {HTMLDomElement}*/this.dom = dom;this.id = id;// 初始化zrender数据层const storage = new Storage();// 渲染方式,默认canvaslet rendererType = opts.renderer || 'canvas';// painterCtors 用来存储渲染方式 见all.tsif (!painterCtors[rendererType]) {// Use the first registered renderer.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.`);}}// default: falseopts.useDirtyRect = opts.useDirtyRect == null? false: opts.useDirtyRect;// 初始化painter 初始化View层const painter = new painterCtors[rendererType](dom, storage, opts, id);const ssrMode = opts.ssr || painter.ssrOnly;this.storage = storage;this.painter = painter;// 初始化Controller相关const handerProxy = (!env.node && !env.worker && !ssrMode)? new HandlerProxy(painter.getViewportRoot(), painter.root): null;// default: falseconst useCoarsePointer = opts.useCoarsePointer;const usePointerSize = (useCoarsePointer == null || useCoarsePointer === 'auto')? env.touchEventsSupported: !!useCoarsePointer;const defaultPointerSize = 44;// 点击元素影响范围大小let pointerSize;if (usePointerSize) {pointerSize = zrUtil.retrieve2(opts.pointerSize, defaultPointerSize);}// 初始化Controller相关this.handler = new Handler(storage, painter, handerProxy, painter.root, pointerSize);// 动画 非ssr模式下绑定animation.stage = this._flush(true), 与刷新渲染相关this.animation = new Animation({stage: {update: ssrMode ? null : () => this._flush(true)}});// 非ssr模式下开始动画if (!ssrMode) {this.animation.start();}}

我们由this.animation.start()定位到animation._startLoop()方法:

    _startLoop() {const self = this;this._running = true;function step() {if (self._running) {requestAnimationFrame(step);!self._paused && self.update();}}requestAnimationFrame(step);}

可见,_startLoop()方法中,只要_running_pausetrue(也就是动画开始且未暂停),就不断通过Animation.update()方法更新渲染页面。更多有关Animation更新方式,我们之后再详细了解。

我们将思绪拉回圆形创建过程,初始化zrender实例后,我们通过new zrender.Circle(opts)创建Circle元素。Circle的实现在src/graphic/shape目录下,您可以在该目录下找到更多图形的实现。

图形元素的继承关系如下,其中Element是画布中元素的最基本单位
在这里插入图片描述

Circle的构造函数调用其父类Element类的构造函数,将传入的参数opts映射到circle实例的属性上,同时Circle类中有buildPath方法如下,该方法定义了如何通过原生canvas画出我们创建的Circle元素。

最后,我们将Circle元素添加到zrender实例中,zrender.add()源码如下:

/**
* 添加元素
*/
add(el: Element) {if (!el) {return;}this.storage.addRoot(el);el.addSelfToZr(this);this.refresh();
}

可以看出add()方法做了3件事:

  1. 保存新元素:通过this.storage.addRoot(el)将元素保存在storage._root数组中
  2. 将元素绑定到zrender实例:el._zr属性绑定到当前zrender实例,如果元素有动画,将动画添加到zrender动画中;将元素自身属性也绑定到zrender实例。
  3. zrender.refresh,调用animation.start()更新渲染

细说如何更新渲染

// Animation.update
update(notTriggerFrameAndStageUpdate?: boolean) {const time = getTime() - this._pausedTime;const delta = time - this._time;let clip = this._head;while (clip) {// Save the nextClip before step.// So the loop will not been affected if the clip is removed in the callbackconst nextClip = clip.next;let finished = clip.step(time, delta);if (finished) {clip.ondestroy();this.removeClip(clip);clip = nextClip;}else {clip = nextClip;}}this._time = time;if (!notTriggerFrameAndStageUpdate) {// 'frame' should be triggered before stage, because upper application// depends on the sequence (e.g., echarts-stream and finish// event judge)this.trigger('frame', delta);this.stage.update && this.stage.update();}}

暂不考虑有动画的复杂场景,最简单情况,zrender触发‘frame’事件,然后this.stage.update()方法中会调用el.buildPath()方法绘制图形:

// Circle.buildPath
buildPath(ctx: CanvasRenderingContext2D, shape: CircleShape) {// Use moveTo to start a new sub path.// Or it will be connected to other subpaths when in CompoundPathctx.moveTo(shape.cx + shape.r, shape.cy);ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2);}

如此圆形绘制成功。

我们也可以用原生实现同样效果:

const canvas = document.createElement("canvas");const el = document.getElementById('container')el.appendChild(canvas);const ctx = canvas.getContext("2d");let shape = {cx: 150,cy: 50,r: 40}ctx.strokeStyle = "#000";ctx.beginPath()ctx.moveTo(shape.cx + shape.r, shape.cy);ctx.arc(shape.cx, shape.cy, shape.r, 0, Math.PI * 2);ctx.stroke()ctx.closePath()

参考文章:

  1. https://ecomfe.github.io/zrender-doc/public/api.html#zrender-api
  2. http://qiuruolin.cn/2019/05/20/echarts-1/#more

http://chatgpt.dhexx.cn/article/4InkmlS4.shtml

相关文章

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

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

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

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

二维绘图引擎ZRender

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

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

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

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

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

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

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

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

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

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

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

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

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

特征值和特征向量(三)

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

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

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

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

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

逆水寒商业脚本制作视频

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

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

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

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

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

游戏多开器制作教程

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

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

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

html5分镜头脚本范例,分镜头脚本教程图解

这是一份pdf免费高清彩版的分镜头脚本设计教程,全书彩页,排版非常不错,阅读起来充满趣味性,另外,书中有不少分镜头脚本范例以及分镜头脚本教程图解,是一本值得一看的好书。全书详细讲解了分镜头是如何制作的…

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

游戏脚本开发第五卷 前言介绍相关技术相关功能项目结构最后结语 前言介绍 本卷具体介绍脚本1.0,相关代码不再展示,该项目全部源码以及相关配置文件可在下方评论区留下QQ邮箱即可领取。 相关技术 JavaFX,主要用于展示页面效果,该技…

群控系统linux脚本,群控系统的自定脚本制作

做为沒有一切技术性累积的***游戏玩家,买这套醉适合,回家了依照使用说明一顿插线,插 好即玩,一小时内拿下,立刻就可以进行实际业务流程。这套系统软件现阶段关键对于手机0信,沒有各种各样目不暇接的作用&am…