最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Echarts-ZRender源码分析(一)

    正文概述 掘金(雷庭)   2021-05-17   775

    1.整体架构

    1.1 基于MVC模式整体架构

    Echarts-ZRender源码分析(一)

    如上图所示,ZRender是整体设计思路是面向对象的MVC模式,视图层负责渲染,控制层负责用户输入交互,数据层负责数据模型的编排与存储,其对应的文件和作用如下:

    • Storage.ts(数据模型):用于存储所有需要绘制的图形数据,并且提供相关数据的LRU缓存机制,提供数据的CURD管理;
    • PainterBase.ts(视图绘制):PainterBase是绘制的基类,系统提供的Canvas、SVG、VML视图绘制类都继承于PainterBase类,用户也可以自行继承实现如webgl的绘制能力;
    • Handler.ts(交互控制):事件交互控制模块,为图形元素实现和HTMLDOMElement一样的事件交互逻辑,如图形的选中、单击、触摸等事件;

    除了上述MVC3大模块以外,还有以下辅助功能模块:

    1.2 辅助功能模块

    • 动画管理模块(animation):管理图形的动画,绘制前会将对象的动画计算成帧对象保存在动画管理器中,伴随着动画触发条件将帧数据推送给视图绘制模块进行动画绘制;
    • 工具类模块(tool、core):提供颜色转换、路径转换、变换矩阵运算、基础事件对象封装、向量计算、基础数据结构等独立辅助计算函数或者类;
    • 图形对象模块(graphic):提供元素的对象类(包含Image、Group、Arc、Rect等),所有元素其最顶层都继承于Element类;
    • 图形对象辅助模块(contain):提供用于判断包含关系的算法,比如:坐标点是否在线上,坐标点是否在图形内;

    2.源码文件结构

    2.1 源码目录结构

    src/
      -|config.ts
      -|Element.ts
      -|Storage.ts
      -|Handler.ts
      -|PainterBase.ts
      -|zrender.ts //入口文件
      -|export.ts
      -|animation/
        -|Animation.ts
        -|Animator.ts
        -|Clip.ts
        ...
      -|canvas/
        -|Painter.ts
        ...
      -|svg/
        -|Painter.ts
        ...
      -|vml/
        -|Painter.ts
        ...
      -|conatin/
        -|arc.ts
        ...
      -|core/
        -|LRU.ts
        -|matrix.ts
        ...
      -|dom/
      -|graphic/
        -|Group.ts
        -|Image.ts
        -|Path.ts
        -|shape/
          -|Arc.ts
          -|Rect.ts
          -|Circle.ts
          ...
        ...
      -|mixin/
        -|Draggable.ts
      -|tool/
        -|color.ts
        -|ParseSVG.ts
        -|parseXML.ts
    

    2.2 目录及文件介绍

    • config.ts:全局配置文件,可配置debug模式、retina屏幕高清优化、深/浅主题色值等
    • Element.ts:所有可绘制图形元素和Group的基类,其中定义了基础属性(如:id,name,type,isGroup等),对象的基础成员方法(hidden,show,animate,animateTo,copyValue等)
    • Storage.ts:M层,对象模型层/存储器层,存储并管理元素对象实例,元素对象实例存储在_displayableList数组中,每次绘制时会根据zlevel->z->插入顺序进行排序,提供添加、删除、清空注销元素对象实例的方法
    • Handler.ts:C层,控制层/器,用于向元素上绑定事件,实现DOM式事件管理机制
    • PainterBase.ts:V层,视图层/渲染器层,PainterBase是渲染器的基类,5.0版本默认提供Canvas、SVG渲染器,5.0版之前版本还提供VML渲染器,元素的绘制就是由渲染器决定,系统默认Canvas渲染器渲染
    • zrender.ts:ZRender入口文件,也是编译主入口,
      • 暴露全局方法:init用于初始化ZRender实例,delInstance用于删除ZRender实例,dispose用于注销某个ZRender实例,disposeAll用于注销所有ZRender实例,registerPainter用于注册新的渲染器
      • ZRender类:用于管理ZRender实例里的所有元素对象实例,存储器(Storage)实例,渲染器(Painter)实例,事件控制器(Handler)实例,动画管理器(Animation)实例
    • export.ts:编译时调用,用于对外导出API
    • animation:存放动画相关的代码文件,如:Animation,Animator等
    • canvas:存放Canvas渲染器相关的代码文件
    • svg:存放svg渲染器相关的程序文件
    • vml:存放vml渲染器相关的程序文件
    • contain:用于补充特殊元素的坐标包含关系计算方法,如贝塞尔曲线上的点包含关系计算
    • core:大杂烩文件夹,我这里把它归纳为工具方法文件,包含LRU缓存,包围盒计算,浏览器环境判断,变换矩阵,触摸事件实现等大杂烩方法
    • dom:仅HandlerProxy.ts一个程序文件,用于实现DOM事件代理,所有画布内元素的事件都是从画布DOM的事件进行代理进入
    • graphic:所有元素的实体对象类都存放在这个文件夹,包含Group,可绘制对象基类Displayable,路径,圆弧,矩形等
    • mixin:仅Draggable.ts一个文件,用于管理元素的拖拽事件,因为Echarts用不上拖拽,所以拖拽事件还没有在ts版本中实现(后面会分享个人实现的版本代码)
    • tool:工具方法,提供颜色计算,SVG路径转换等工具类

    3.入口文件源码分析(zrender.ts)

    3.1 ZRender全局暴露的方法

    zrender.ts中对外暴露的全局方法(见如下代码注释),全局方法可通过zrender.xxx即可调用,如:zrender.init()

    全局方法主要用于管理ZRender实例(初始化,删除,查找,注销等操作)

    // 用于存放渲染器
    const painterCtors: Dictionary<PainterBaseCtor> = {};
    
    // 用于存放ZRender实例,后文对于实例统称zr
    let instances: { [key: number]: ZRender } = {};
    
    /**
     * 按id删除ZRender实例
     */
    function delInstance(id: number) {
      // 代码省略
    }
    
    /**
     * 初始化ZRender实例,需要传入dom节点作为canvas父级
     */
     export function init(dom: HTMLElement, opts?: ZRenderInitOpt) {
      const zr = new ZRender(zrUtil.guid(), dom, opts);
      instances[zr.id] = zr;
      return zr;
    }
    
    /**
    * 注销zr实例,注销后会将zr实例内的图形全部删除,不可恢复
    */
    export function dispose(zr: ZRender) {
      zr.dispose();
    }
    
    /**
    * 注销ZRender中管理的所有zr实例
    */
    export function disposeAll() {
      // 代码省略
    }
    
    /**
    * 通过实例id获取zr实例
    */
    export function getInstance(id: number): ZRender {
      return instances[id];
    }
    
    /**
     * 注册渲染器,系统在启动时会默认注册Canvas和SVG渲染器
     */
    export function registerPainter(name: string, Ctor: PainterBaseCtor) {
      painterCtors[name] = Ctor;
    }
    
    class ZRender {
      // 后文详解
    }
    

    3.2 ZRender对象类

    ZRender类写在入口文件zrender.ts中,本节通过对代码精简加注释的方式进行源码分析,精简源文件代码为了便于读者理解

    class ZRender {
      // 画布渲染的容器根节点,必须是一个HTML元素
      dom: HTMLElement
      // zr实例id
      id: number
      // 存储器对象实例
      storage: Storage
      // 渲染器对象实例
      painter: PainterBase
      // 控制器对象实例
      handler: Handler
      // 动画管理器对象实例
      animation: Animation
    
      constructor(id: number, dom: HTMLElement, opts?: ZRenderInitOpt) {
        // 初始化容器根节点
        this.dom = dom;
        // 全局init函数会生成guid传入
        this.id = id;
        // new存储器实例
        const storage = new Storage();
        // 默认渲染器类型为canvas
        let rendererType = opts.renderer || 'canvas';
        // 创建渲染器
        const painter = new painterCtors[rendererType](dom, storage, opts, id);
        // 将存储器实例赋值给成员变量(作者认为这步是脱了裤子放屁,还多const一个storage变量)
        this.storage = storage;
        // 将渲染器赋值给成员变量
        this.painter = painter;
        // 创建事件管理器
        this.handler = new Handler(storage, painter, handerProxy, painter.root);
        // 创建动画管理器并启动动画管理程序
        this.animation = new Animation({
          stage: {
            update: () => this._flush(true)
          }
        });
        this.animation.start();
      }
    
      /**
       * 向画布添加元素,等待下一帧渲染
       */
      add(el: Element) {
        // 代码省略,后续的方法体代码如果无特别说明都会省略
      }
    
      /**
       * 从存储器中间元素删除,下一帧该元素将不会被渲染
       */
      remove(el: Element) { }
    
      /**
       * 配置图层顺序、开启动态模糊等
       */
      configLayer(zLevel: number, config: LayerConfig) { }
    
      /**
       * 设置画布背景色
       */
      setBackgroundColor(backgroundColor: string | GradientObject | PatternObject) { }
    
      /**
       * 获取画布背景色
       */
      getBackgroundColor() { }
    
      /**
       * 将zr强制设置为深色模式
       */
      setDarkMode(darkMode: boolean) { }
    
      /**
       * 查询当前zr是否深色模式
       */
      isDarkMode() { }
    
      /**
       * 执行强制刷新画布
       */
      refreshImmediately(fromInside?: boolean) { }
    
      /**
       * 执行下一帧刷新画布
       */
      refresh() { }
    
      /**
       * 执行所有刷新操作
       */
      flush() {
        this._flush(false);
      }
    
      /**
       * 设置动画静止帧数,动画将会在设置的帧数后停止执行
       */
      setSleepAfterStill(stillFramesCount: number) {
        this._sleepAfterStill = stillFramesCount;
      }
    
      /**
       * 唤醒动画,等下次渲染时执行
       */
      wakeUp() { }
    
      /**
       * 下一帧显示鼠标悬浮状态
       */
      refreshHover() { }
    
      /**
       * 强制执行鼠标悬浮状态
       */
      refreshHoverImmediately() { }
    
      /**
       * 调整画布大小
       */
      resize(opts?: {
        width?: number | string
        height?: number | string
      }) { }
    
      /**
       * 强制停止并清空动画
       */
      clearAnimation() { }
    
      /**
       * 获取画布宽度
       */
      getWidth(): number { }
    
      /**
       * 获取画布高度
       */
      getHeight(): number { }
    
      /**
       * 将路径绘制成图片,提高绘制性能
       */
      pathToImage(e: Path, dpr: number) { }
    
      /**
       * 设置鼠标样式
       * @param cursorStyle='default' 例如 crosshair
       */
      setCursorStyle(cursorStyle: string) { }
    
      /**
       * 查找鼠标当前位置元素的对象实例
       */
      findHover(x: number, y: number): {
        target: Displayable
        topTarget: Displayable
      } { }
    
      /**
       * 挂载全局事件,这里是ts的on方法多态
       */
      on<Ctx>(eventName: ElementEventName, eventHandler: ElementEventCallback<Ctx, unknown>, context?: Ctx): this
      on<Ctx>(eventName: string, eventHandler: EventCallback<Ctx, unknown>, context?: Ctx): this
      on<Ctx>(eventName: string, eventHandler: EventCallback<Ctx, unknown> | EventCallback<Ctx, unknown, ElementEvent>, context?: Ctx): this { }
    
      /**
       * 卸载全局事件
       */
      off(eventName?: string, eventHandler?: EventCallback<unknown, unknown> | EventCallback<unknown, unknown, ElementEvent>) { }
    
      /**
       * 按照事件名称手动触发事件
       */
      trigger(eventName: string, event?: unknown) { }
    
    
      /**
       * 清空画布及其已绘制的图形元素
       */
      clear() { }
    
      /**
       * 将ZRender对象注销
       */
      dispose() { }
    }
    

    4.通过案例分析ZRender工作流程

    4.1 案例

    下面代码是绘制一个半径为30px的玫红色(色值#FF6EBE)圆形,并为圆形绑定左右移动循环动画。

    // 1.申明绘制ZRender实例的DOM容器
    let container = document.getElementsById('example-container')[0];
    // 2.初始化ZRender实例zr、同时zr会绘制画布与container同宽高
    let zr = zrender.init(container);
    
    // 3.获取zr画布的宽高
    let w = zr.getWidth();
    let h = zr.getHeight();
    
    // 4.设定圆的半径为30px
    let r = 30;
    
    // 5.创建圆形对象实例cr
    let cr = new zrender.Circle({
      shape: {
        cx: r,
        cy: h / 2,
        r: r
      },
      style: {
        fill: 'transparent',
        stroke: '#FF6EBE'
      },
      silent: true
    });
    
    // 6.为圆cicle绑定形状动画,参数true表示循环执行
    cr.animate('shape', true)
      .when(5000, {
        cx: w - r
      })
      .when(10000, {
        cx: r
      })
      .start();
    
    // 7.将圆形对象实例cricle添加到zr实例中进行渲染
    zr.add(cr);
    

    Echarts-ZRender源码分析(一)

    4.2 ZRender绘制流程

    这一节主要结合2.1的案例讲ZRender如何进行绘制和运行动画流程

    Echarts-ZRender源码分析(一)

    1. 创建ZRender实例:使用const zr = zrender.init(),可多zr实例,每实例拥有自己的画布
    2. 创建需要绘制的图形实例,图形类名可通过zrender.xxx获得,其中xxx为图形类名
    3. zr.add方法将图形实例添加到存储器
    // zrender.ts
    add(el: Element) {
      // 将el(这里为cr实例)添加到存储器
      this.storage.addRoot(el);
    
      // 并且将动画放入动画管理器
      el.addSelfToZr(this);
    
      // 启动绘制程序
      this.refresh();
    }
    

    3B、C、D. 将图形上绑定的动画添加到动画管理器,生成动画帧,启动动画绘制

    1. zr实例化时就已经启动逐帧扫描程序,只是这里存储器有可渲染的元素被捕获后才会执行渲染动作
    // zrender.ts
    class Zrender {
      constructor() {
        this.animation = new Animation({
          stage: {
            // 将渲染程序绑定到帧渲染策略
            update: () => this._flush(true)
          }
        });
        // 启动动画管理器,启动帧渲染扫描rAF程序
        this.animation.start();
      }
    
      // 下一帧执行渲染
      _flush() {
        this.refreshHoverImmediately();
      }
    
      // 强制渲染
      refreshHoverImmediately() {
    
        // 调用渲染器渲染程序
        this.painter.refresh();
      }
    }
    
    1. 上一步的this.panter.refresh()会请求storage去获取渲染列表
    // canvas/Painter.ts
    class Painter {
      refresh() {
        // 获取渲染列表
        const list = this.storage.getDisplayList(true);
      }
    }
    
    // Storage.ts
    class Storage {
      /**
        * 更新图形的绘制队列。
        * 每次绘制前都会调用,该方法会先深度优先遍历整个树,
        * 更新所有Group和Shape的变换并且把所有可见的Shape保存到数组中,
        * 最后根据绘制的优先级(zlevel > z > 插入顺序)排序得到绘制队列
        */
      getDisplayList() {
        // 返回渲染列表
        return this._displayList
      }
    }
    
    1. 6、7启动并执行渲染程序,进行图形的路径绘制,主要方法为:doPaintList->doPaintEl

    本章节END.


    起源地下载网 » Echarts-ZRender源码分析(一)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元