zrender源码学习笔记(一):认识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();
        // 渲染方式,默认canvas
        let rendererType = opts.renderer || 'canvas';
        // painterCtors 用来存储渲染方式 见all.ts
        if (!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: false
        opts.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: false
        const 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 callback
            const 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 CompoundPath
        ctx.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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Libevent是一个事件驱动的网络编程框架,而event.h是其核心头文件之一。该头文件定义了事件处理相关的结构体、函数和宏等内容。 下面是event.h中常用的一些定义和函数: ### 1.事件回调函数 ```c typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void *arg); ``` 该类型定义了事件回调函数的原型,其中fd是事件所在的文件描述符,events是事件类型,arg是用户传入的参数。 ### 2.事件结构体 ```c struct event { event_callback_fn ev_callback; // 事件回调函数 int ev_fd; // 事件所在的文件描述符 short ev_events; // 事件类型 short ev_res; // 事件结果 struct event_base *ev_base; // 事件所属的event_base void *ev_arg; // 用户传入的参数 }; ``` 该结构体表示一个事件,其中ev_callback是事件回调函数,ev_fd是事件所在的文件描述符,ev_events是事件类型,ev_res是事件结果,ev_base是事件所属的event_base,ev_arg是用户传入的参数。 ### 3.事件类型 ```c #define EV_TIMEOUT 0x01 #define EV_READ 0x02 #define EV_WRITE 0x04 #define EV_SIGNAL 0x08 #define EV_PERSIST 0x10 #define EV_ET 0x20 ``` 该宏定义了事件类型,分别为超时事件、读事件、写事件、信号事件、持续事件和边缘触发事件。 ### 4.事件处理函数 ```c struct event_base *event_base_new(void); int event_base_dispatch(struct event_base *base); int event_base_loopexit(struct event_base *base, const struct timeval *tv); void event_base_free(struct event_base *base); ``` 这些函数用于创建event_base、处理事件、退出事件循环和释放event_base等操作。 以上是event.h中的一些常用内容,更多细节可以查看源码和官方文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值