Closed vieyahn2017 closed 5 years ago
zrender源码分析1:总体结构 https://www.cnblogs.com/hhstuhacker/p/zrender-source-advance-frame.html
zrender(Zlevel Render) 是一个轻量级的Canvas类库,这里是GitHub的网址 点我, 类似的类库有Kinetic.JS、EaselJS。 但貌似都没有zrender好用(可能是更加符合国人的习惯),强大的图表工具echarts就是在zrender基础上建立, 用zrender和echarts做了两个关于canvas的两个可视化项目之后,忍不住看了下zrender的项目代码(也有需要修改源代码的缘故), 但是翻开之后,代码的结构比较清晰,注释也都是中文,比较容易读懂。最大的感想就是,沒有像jQuery之类的对于代码极(bian)度(tai)的精简追求,各种意图一看便知。 如果对于zrender的api不熟悉,请移步github上,这里只对源码进行了一些详(qian)细(xian)的分析。
关于总体结构,最贴切的描述恐怕也没这一张图来的爽: zrender结构图 从github将项目clone下来之后,打开src/zrender.js之后,有如下发现:
var _instances = {}; //ZRender实例map索引
var zrender = {};
zrender.version = '2.0.0';
/**
* zrender初始化
* 不让外部直接new ZRender实例,为啥?
* 不为啥,提供全局可控同时减少全局污染和降低命名冲突的风险!
*
* @param {HTMLElement} dom dom对象,不帮你做document.getElementById了
* @param {Object=} params 个性化参数,如自定义shape集合,带进来就好
*
* @return {ZRender} ZRender实例
*/
zrender.init = function(dom, params) {
var zi = new ZRender(guid(), dom, params || {});
_instances[zi.id] = zi;
return zi;
};
我想这里已经很明显,用zrender.init(dom)初始化的时候,直接new一个内部的ZRender对象进行返回,原因我想作者已经写的很明白了(提供全局可控同时减少全局污染和降低命名冲突的风险!) 总比每次new ZRender()好多了,最起码看起来是这样,并且把每个实例就行保存,便于维护。 至于周围的其他的三个方法,dispose,getInstance,delInstance 就没什么可说的了,不过在项目中,也怎么用得上。
/**
* zrender实例销毁,记在_instances里的索引也会删除了
* 管生就得管死,可以通过zrender.dispose(zi)销毁指定ZRender实例
* 当然也可以直接zi.dispose()自己销毁
*
* @param {ZRender=} zi ZRender对象,不传则销毁全部
*/
zrender.dispose = function (zi) {
if (zi) {
zi.dispose();
}
else {
for (var key in _instances) {
_instances[key].dispose();
}
_instances = {};
}
return zrender;
};
/**
* 获取zrender实例
*
* @param {string} id ZRender对象索引
*/
zrender.getInstance = function (id) {
return _instances[id];
};
/**
* 删除zrender实例,ZRender实例dispose时会调用,
* 删除后getInstance则返回undefined
* ps: 仅是删除,删除的实例不代表已经dispose了~~
* 这是一个摆脱全局zrender.dispose()自动销毁的后门,
* take care of yourself~
*
* @param {string} id ZRender对象索引
*/
zrender.delInstance = function (id) {
delete _instances[id];
return zrender;
};
\\接下来就是核心的ZRender构造函数,这里可以很清晰的看到M(Storage)V(Painter)C(Handler)的结构.
/**
* ZRender接口类,对外可用的所有接口都在这里!!
* storage(M)、painter(V)、handler(C)为内部私有类,外部接口不可见
* 非get接口统一返回支持链式调用~
*
* @param {string} id 唯一标识
* @param {HTMLElement} dom dom对象,不帮你做document.getElementById
*
* @return {ZRender} ZRender实例
*/
function ZRender(id, dom) {
this.id = id;
this.env = require('./tool/env');
this.storage = new Storage();
this.painter = new Painter(dom, this.storage);
this.handler = new Handler(dom, this.storage, this.painter);
// 动画控制
this.animatingShapes = [];
this.animation = new Animation({
stage : {
update : getAnimationUpdater(this)
}
});
this.animation.start();
}
Storage只是JS对象级别的对于Shape图形的增(add/addHover)删(del,delHover)改(mod)查(get/iterShape/getMaxZlevel等),更像一个数据结构的东西 Painter负责真正的绘图操作,这里是比较繁重的部分 1.负责canvas及其周边DOM元素的创建与处理 2.负责调用各个Shape(预定义好的)进行绘制 3.提供基本的操作方法,渲染(render)、刷新(refresh)、尺寸变化(resize)、擦除(clear)等 Handler负责事件处理,解决基础的浏览器兼容问题、进行事件的注册与转发、拖动 至于附加在ZRender的prototype的其他方法,除了关于动画部分的,其他的都是调用的Storage、Painter、Handler,这里就不再赘述了。
ZRender源码分析2:Storage(Model层)
https://www.cnblogs.com/hhstuhacker/p/zrender-source-storage-advance.html
ZRender源码分析3:Painter(View层)-上
https://www.cnblogs.com/hhstuhacker/p/zrender-source-painter-part1.html
ZRender源码分析4:Painter(View层)-中
https://www.cnblogs.com/hhstuhacker/p/zrender-source-painter-part2.html
ZRender源码分析5:Shape绘图详解
https://www.cnblogs.com/hhstuhacker/p/zrender-source-painter-shape.html
ZRender源码分析6:Shape对象详解之路径
https://www.cnblogs.com/hhstuhacker/p/zrender-source-painter-shape-path.html
目前zrender已经更新到4的版本了, 上述帖子多多少少都有些过时了。
ECharts 3.0底层zrender 3.x源码分析1-总体架构
2017年01月11日 10:41:23 TechFE 阅读数:9688 标签: zrender ECharts 个人分类: ECharts zrender 版权声明:本文为博主原创文章,转载请注明出处和原文链接。 https://blog.csdn.net/future_todo/article/details/54341386 zrender是一个轻量级的Canvas类库,作为百度Echarts 3.0的底层基础。截至目前查看的zrender源码和文档,包括官网文档都还停留在2.x时代,我打算用一个系列介绍下zrender 3.x的使用和源码,一些demo和没有在博客中介绍的源码请进我的github仓库。
https://github.com/zrysmt/echarts3/tree/master/zrender
基于版本 3.2.2。
官网上的一张图和解释。
MVC结构分别在Stroage.js,Painter.js,Handler.js文件下,我们稍后会详细解释,现在我们大概来看下它们分别的作用。
源码结构
目录的介绍
类似于jquery的无new化处理,init调用即可 调用:
var zr = zrender.init(document.getElementById('main'));
\\源码:
var instances = {}; // ZRender实例map索引
var zrender = {};
zrender.init = function(dom, opts) {
var zr = new ZRender(guid(), dom, opts);
instances[zr.id] = zr;
return zr;
};
我们可以在构造函数中,看到MVC的管理机制。
var ZRender = function(id, dom, opts) {
opts = opts || {};
this.dom = dom;
this.id = id;
var self = this;
var storage = new Storage();
var rendererType = opts.renderer;
if (useVML) {//IE中使用VML渲染
if (!painterCtors.vml) {
throw new Error('You need to require \'zrender/vml/vml\' to support IE8');
}
rendererType = 'vml';
} else if (!rendererType || !painterCtors[rendererType]) {
rendererType = 'canvas';
}
var painter = new painterCtors[rendererType](dom, storage, opts);
this.storage = storage;//M
this.painter = painter;//V
var handerProxy = !env.node ? new HandlerProxy(painter.getViewportRoot()) : null;
this.handler = new Handler(storage, painter, handerProxy, painter.root);//C
console.log(this);//这里是我增加的为了调试使用的
/**
* @type {module:zrender/animation/Animation}
* 动画控制
*/
this.animation = new Animation({
stage: {
update: zrUtil.bind(this.flush, this)
}
});
this.animation.start();
this._needsRefresh;
// 修改 storage.delFromMap, 每次删除元素之前删除动画
var oldDelFromMap = storage.delFromMap;
var oldAddToMap = storage.addToMap;
storage.delFromMap = function(elId) {
var el = storage.get(elId);
oldDelFromMap.call(storage, elId);
el && el.removeSelfFromZr(self);
};
storage.addToMap = function(el) {
oldAddToMap.call(storage, el);
el.addSelfToZr(self);
};
};
具体的方法及其注释可以在我的github中查看,这里只将方法名放在这里。
ZRender.prototype = {
constructor: ZRender,
/**
* 获取实例唯一标识
* @return {string}
*/
getId: function () {},
/**
* 添加元素后就会渲染
* @param {module:zrender/Element} el
*/
add: function (el) {
this.storage.addRoot(el);
this._needsRefresh = true;
},
/**
* 删除元素
* @param {module:zrender/Element} el
*/
remove: function (el) { },
configLayer: function (zLevel, config) {},
/** Repaint the canvas immediately*/
refreshImmediately: function () {},
/** Mark and repaint the canvas in the next frame of browser*/
refresh: function() {},
flush: function () {},
/**Add element to hover layer */
addHover: function (el, style) {},
/** Add element from hover layer
* @param {module:zrender/Element} el
*/
removeHover: function (el) {},
/** Clear all hover elements in hover layer*/
clearHover: function () {},
/** Refresh hover in next frame*/
refreshHover: function () {},
/**Refresh hover immediately*/
refreshHoverImmediately: function () { ;
},
resize: function(opts) {},
clearAnimation: function () {},
/** Get container width */
getWidth: function() {},
getHeight: function() {},
/** Converting a path to image */
pathToImage: function(e, width, height) {},
/**
* Set default cursor
* @param {string} [cursorStyle='default'] 例如 crosshair
*/
setCursorStyle: function (cursorStyle) {},
/**发布订阅模式 */
on: function(eventName, eventHandler, context) {},
off: function(eventName, eventHandler) {},
trigger: function (eventName, event) {},
/** Clear all objects and the canvas */
clear: function () {},
/** Dispose self */
dispose: function () {}
};
源码的方法,我们以add举例子,它其实调用的是this.storage.addRoot方法,使用MVC机制处理。 使用示例:
var circle1 = new Circle({
shape: {
cx: 100,
cy: 100,
r: 30
},
style: {
fill: 'blue'
},
draggable: true
});
zr.add(circle1);
circle1.on('mouseover', function() {
zr.addHover(this, {
stroke: 'yellow',
lineWidth: 10,
opacity: 1
});
zr.refresh();
});
circle1.on('mouseout', function() {
zr.removeHover(this);
});
注意:这里有addHover方法,所以会渲染两个canvas。如果没有addHover,就只会渲染一个canvas。
MVC对应三个文件的结构很简单,其实就是一个构造函数,一个prototype原型扩展。
我们看构造函数,将元素存储在this._elements(对象)、this._roots(数组)和this._displayList(数组)中,然后负责在其中进行增(addRoot,addToMap)删(delRoot,delFromMap)改(updateDisplayList)查(get,getDisplayList)。
var Storage = function () {
// 所有常规形状,id索引的map
this._elements = {};
//和this._elements存放的元素一样,只不过是数组
this._roots = [];
//和this.roots一样
this._displayList = [];
//this._displayList的长度
this._displayListLen = 0;
};
Handler负责事件处理,包括’click’, ‘dblclick’, ‘mousewheel’, ‘mouseout’, ‘mouseup’, ‘mousedown’, ‘mousemove’, ‘contextmenu’等。我们知道canvas API没有提供监听每个元素的机制,这就需要一些处理。处理的思路是:监听事件的作用坐标(如点击时候的坐标),判断在哪个绘制元素的范围中,如果在某个元素中,这个元素就监听该事件。具体的思路可以查看参考阅读给的链接文章。
Handler.prototype = {
mousemove:function (event){}//... ...
}
util.mixin(Handler, Eventful);//混入,下面我们会解释到
util.mixin(Handler, Draggable);
Painter负责真正的绘图操作,这里是比较繁重的部分
Painter是调用canvas API实现的绘制,包括颜色,渐变色,变换,矩阵变化,绘制图片、文本等。IE8使用excanvas兼容。
设计模式的总结,我在一篇博客中有写,要想看这方面的知识,可以在这里看。
AMD即是“异步模块定义”的意思,所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。源码的结构是这样的
define(function(require) {
return ZRender;
}
使用
require([module], callback);
我们的Demo使用的是百度封装好的AMD模式esl.js(或者使用requirejs也可以),引入方式和使用示例如下:
<script src="../libs/esl.js"></script>
require(['zrender', 'zrender/graphic/shape/Circle', 'zrender/graphic/shape/Polygon'],
function(zrender, Circle, Polygon) { //... ...
});
在core->util.js,主要的思想就是将子类的prototype指向父类的prototype;子类的构造函数之指向自己。
function inherits(clazz, baseClazz) {
var clazzPrototype = clazz.prototype;
function F() {}
F.prototype = baseClazz.prototype;
clazz.prototype = new F();
for (var prop in clazzPrototype) {//属性也继承了
clazz.prototype[prop] = clazzPrototype[prop];
}
clazz.prototype.constructor = clazz;
clazz.superClass = baseClazz;//superClass是个自己定义的属性
}
另外不要忘了,在构造函数中应该重写父类的属性。
function Displayable(opts) {
Element.call(this, opts);
}
调用
zrUtil.inherits(Displayable, Element);
简而言之,混入就是将一个对象的方法复制给另外一个对象。实现在util.js中
function mixin(target, source, overlay) {
target = 'prototype' in target ? target.prototype : target;
source = 'prototype' in source ? source.prototype : source;
defaults(target, source, overlay);
}
function defaults(target, source, overlay) {
for (var key in source) {
if (source.hasOwnProperty(key) && (overlay ? source[key] != null : target[key] == null)) {
target[key] = source[key];
}
}
return target;
}
调用
zrUtil.mixin(Displayable, RectText);
实现很简单,类似混入模式,将source对象的方法复制给target对象。
function extend(target, source) {
for (var key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
return target;
}
逻辑在mixin文件夹中的Eventful.js,为Handle(handle.js)混入方法
util.mixin(Handler, Eventful); 包括一下几种方法
步进关系 –>为扩展或混入,==>为继承自父类,()内部为所在位置, [ ]为扩展或者混入的方式。
Element[Animatable Transformable Eventful] (Element.js) ==>
Displayable[ReactText] (Displayable.js) ==>
Path[Sub] (Path.js) ==>
Sub(Path.js) –>
各类型的shape
底层对象是封装过的Element。
绘制的逻辑
add(zrender.js)–>addRoot(Storage.js) –> addToMap(Storage.js) –>
dirty[标记为脏的,下一帧渲染] (path.js) –> refresh(Painter.js)–>_paintList[遍历_displayList] (Painter.js)–>
_doPaintEl[渲染单个元素] Painter.js) –>brush(Path.js)–>buildPath (各个类型的shape)
ECharts 3.0底层zrender 3.x源码分析2-Painter(V层)
2017年01月11日 10:45:36 TechFE 阅读数:5198 标签: ECharts zrender 个人分类: ECharts zrender 版权声明:本文为博主原创文章,转载请注明出处和原文链接。 https://blog.csdn.net/future_todo/article/details/54341426 上一篇介绍了zrender的总体结构,这一篇我们就详细介绍View层–Painter(Painter.js)。
一些demo和没有在博客中介绍的源码请进我的github仓库。
https://github.com/zrysmt/echarts3/tree/master/zrender
Painter利用canvas负责真正的绘图操作。
两个例子都是渲染到div上。
<div id="main" style="width:1000px;height:600px;margin:0;"></div>
zrender 3.x版本渲染结果(demo/demo1/demo3-chart.html)
我们可以看到渲染结果都会新建一层div(从下面的分析我们可以得到这个div就是_domRoot),里面嵌套canvas。如果有使用addHover(有hover层,data-zr-dom-id=”zr_100000”)的话,hover层会单独列一个canvas画布。
sector.on('mouseover', function() {
zr.addHover(this, {
stroke: 'yellow',
lineWidth: 10,
opacity: 1
});
zr.refresh();
});
sector.on('mouseout', function() {
zr.removeHover(this);
});
<div id="main" style="width: 1000px; height: 600px; margin: 0px; -webkit-tap-highlight-color: transparent; user-select: none;">
<div style="position: relative; overflow: hidden; width: 1000px; height: 600px; padding: 0px; margin: 0px; border-width: 0px; cursor: default;">
<canvas width="1000" height="600" data-zr-dom-id="zr_0" style="position: absolute; left: 0px; top: 0px; width: 1000px; height: 600px; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); padding: 0px; margin: 0px; border-width: 0px;"></canvas>
<canvas width="1000" height="600" data-zr-dom-id="zr_100000" style="position: absolute; left: 0px; top: 0px; width: 1000px; height: 600px; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); padding: 0px; margin: 0px; border-width: 0px;"></canvas>
</div>
</div>
var Painter = function (root, storage, opts) {
// In node environment using node-canvas
var singleCanvas = !root.nodeName // In node ?
|| root.nodeName.toUpperCase() === 'CANVAS';
this._opts = opts = util.extend({}, opts || {});
this.dpr = opts.devicePixelRatio || config.devicePixelRatio;
this._singleCanvas = singleCanvas;
/**
* 绘图容器
* @type {HTMLElement}
*/
this.root = root;
var rootStyle = root.style;
if (rootStyle) {
rootStyle['-webkit-tap-highlight-color'] = 'transparent';
rootStyle['-webkit-user-select'] =
rootStyle['user-select'] =
rootStyle['-webkit-touch-callout'] = 'none';
root.innerHTML = '';
}
/**
* @type {module:zrender/Storage}
*/
this.storage = storage;
/**
* 存储图层画布,这个变量很重要
* @type {Array.<number>}
* @private
*/
var zlevelList = this._zlevelList = [];
/** 图层
* @type {Object.<string, module:zrender/Layer>}
* @private
*/
var layers = this._layers = {};
this._layerConfig = {};
if (!singleCanvas) {//没有画布,就使用div
this._width = this._getSize(0);
this._height = this._getSize(1);
var domRoot = this._domRoot = createRoot(
this._width, this._height
);
root.appendChild(domRoot);
}
else {//已经有块画布
// Use canvas width and height directly
var width = root.width;
var height = root.height;
this._width = width;
this._height = height;
// Create layer if only one given canvas
// dpr设置为1,是因为canvas已经定了宽和高
var mainLayer = new Layer(root, this, 1);
mainLayer.initContext();
// FIXME Use canvas width and height
// mainLayer.resize(width, height);
layers[0] = mainLayer;
zlevelList.push(0);
}
this.pathToImage = this._createPathToImage();
// Layers for progressive rendering
this._progressiveLayers = [];
this._hoverlayer;
this._hoverElements = [];
};
Painter.prototype = {
constructor: Painter,
isSingleCanvas: function() {},
getViewportRoot: function() {},
refresh: function(paintAll) {},
addHover: function(el, hoverStyle) {},
removeHover: function(el) {},
clearHover: function(el) {},
refreshHover: function() {},
_startProgessive: function() {},
_clearProgressive: function() {},
_paintList: function(list, paintAll) {},
_doPaintList: function(list, paintAll) {},
_doPaintEl: function(el, currentLayer, forcePaint, scope) {},
getLayer: function(zlevel) {},
insertLayer: function(zlevel, layer) {},
eachLayer: function(cb, context) {},
eachBuildinLayer: function(cb, context) {},
eachOtherLayer: function(cb, context) {},
getLayers: function() {},
_updateLayerStatus: function(list) {},
clear: function() {},
_clearLayer: function(layer) {},
configLayer: function(zlevel, config) {},
delLayer: function(zlevel) {},
resize: function(width, height) {},
clearLayer: function(zlevel) {},
dispose: function() {},
getRenderedCanvas: function(opts) {},
getWidth: function() {},
getHeight: function() {},
_getSize: function(whIdx) {},
_pathToImage: function(id, path, width, height, dpr) {},
_createPathToImage: function() {}
}
我们再来回顾下整个渲染的过程:
add(zrender.js)–>addRoot(Storage.js) –> addToMap(Storage.js) –>
dirty[标记为脏的,下一帧渲染] (path.js) –> refresh(Painter.js)–>_paintList[遍历_displayList] (Painter.js)–>
_doPaintEl[渲染单个元素] Painter.js) –>brush(Path.js)–>buildPath (各个类型的shape)
refresh刷新,刷新去绘制
/**
* 刷新
* @param {boolean} [paintAll=false] 强制绘制所有displayable
*/
refresh: function(paintAll) {
var list = this.storage.getDisplayList(true); //要绘制的图形
var zlevelList = this._zlevelList;
this._paintList(list, paintAll); //去绘制
// Paint custum layers 绘制layer层
for (var i = 0; i < zlevelList.length; i++) {
var z = zlevelList[i];
var layer = this._layers[z];
if (!layer.isBuildin && layer.refresh) {
layer.refresh();
}
}
this.refreshHover(); //刷新hover层
if (this._progressiveLayers.length) {
this._startProgessive();
}
return this;
}
_paintList
_paintList: function(list, paintAll) {
if (paintAll == null) {
paintAll = false;
}
this._updateLayerStatus(list);
this._clearProgressive();
this.eachBuildinLayer(preProcessLayer);
this._doPaintList(list, paintAll); //全部标注为脏的渲染【dirty(false)】
this.eachBuildinLayer(postProcessLayer);
}
_doPaintList 注意这里已经遍历了(遍历的是_displayList数组),所以后面的只针对单个元素绘制即可。
//... ...
for (var i = 0, l = list.length; i < l; i++) {
//... ...
this._doPaintEl(el, currentLayer, paintAll, scope);//绘制每个元素
}
_doPaintEl
//... ...
el.brush(ctx, scope.prevEl || null);//在Path.js中的方法brush
这一系列的操作是:
创建canvas外层包裹着_domRoot(div)
canvas要绘制的东西都存储在storage中的_displayList数组中
遍历_displayList
最后调用buildPath的canvas绘制。
如第1部分所见,如果增加了hover层(addHOver方法),那么会增加一层canvas,现在就来看这一层canvas是如何作用的。
addHover(zrender.js)–>
addHover(zrender.js)
addHover: function(el, style) {
if (this.painter.addHover) {
this.painter.addHover(el, style);
this.refreshHover();
}
}
addHover(Painter.js)
addHover: function(el, hoverStyle) {
if (el.__hoverMir) {
return;
}
var elMirror = new el.constructor({
style: el.style,
shape: el.shape
});
elMirror.__from = el;
el.__hoverMir = elMirror;
elMirror.setStyle(hoverStyle);
this._hoverElements.push(elMirror);
//存放到this._hoverElements(数组)
}
我们在第三部分已经看到refresh方法中的refreshHover,渲染canvas时候,会渲染两个canvas,一个是主canvas,一个是hover层canvas,第二个canvas就是使用refreshHover方法。
refreshHover(Painter.js)
refreshHover: function() {
var hoverElements = this._hoverElements;
var len = hoverElements.length;
var hoverLayer = this._hoverlayer;
hoverLayer && hoverLayer.clear();
if (!len) {
return;
}
timsort(hoverElements, this.storage.displayableSortFunc);
if (!hoverLayer) {//不存在则会新创建一层canvas
hoverLayer = this._hoverlayer = this.getLayer(1e5);
}
var scope = {};
hoverLayer.ctx.save();
for (var i = 0; i < len;) {
var el = hoverElements[i];
var originalEl = el.__from;
if (!(originalEl && originalEl.__zr)) {
hoverElements.splice(i, 1);
originalEl.__hoverMir = null;
len--;
continue;
}
i++;
if (!originalEl.invisible) {
el.transform = originalEl.transform;
el.invTransform = originalEl.invTransform;
el.__clipPaths = originalEl.__clipPaths;
// el.
this._doPaintEl(el, hoverLayer, true, scope);//同第3部分
}
}
hoverLayer.ctx.restore();
}
参考阅读:
阅读更多 想对作者说点什么? 我来说一句
ECharts 3.0底层zrender 3.x源码分析3-Handler(C层)
2017年01月11日 10:48:15 TechFE 阅读数:2563 标签: ECharts zrender 个人分类: ECharts zrender 版权声明:本文为博主原创文章,转载请注明出处和原文链接。 https://blog.csdn.net/future_todo/article/details/54341458 这一篇,介绍下Handler处理机制。
Handler负责事件处理,包括’click’, ‘dblclick’, ‘mousewheel’, ‘mouseout’, ‘mouseup’, ‘mousedown’, ‘mousemove’, ‘contextmenu’等。我们知道canvas API没有提供监听每个元素的机制,这就需要一些处理。处理的思路是:监听事件的作用坐标(如点击时候的坐标),判断在哪个绘制元素的范围中,如果在某个元素中,这个元素就监听该事件。
一些demo和没有在博客中介绍的源码请进我的github仓库。
https://github.com/zrysmt/echarts3/tree/master/zrender
同样Handle.js文件的结构是一个构造函数,一个prototype扩展原型,一些混入模式。
我们首先看在入口(zrender.js)中的调用
var handerProxy = !env.node ? new HandlerProxy(painter.getViewportRoot()) : null;//env.node默认为false
//HandlerProxy 是移动端的一些处理事件
this.handler = new Handler(storage, painter, handerProxy, painter.root);
构造函数:
var Handler = function(storage, painter, proxy, painterRoot) {
Eventful.call(this);
this.storage = storage;
this.painter = painter;
this.painterRoot = painterRoot;
proxy = proxy || new EmptyProxy();
/**
* Proxy of event. can be Dom, WebGLSurface, etc.
*/
this.proxy = proxy;
// Attach handler
proxy.handler = this;
this._hovered;
/**
* @private
* @type {Date}
*/
this._lastTouchMoment;
this._lastX;//坐标位置x
this._lastY;//坐标位置y
Draggable.call(this);
util.each(handlerNames, function (name) {
proxy.on && proxy.on(name, this[name], this);
}, this);
};
构造函数中保留的有坐标信息。
prototype中的一个重要的方法dispatchToElement,针对目标图形元素触发事件。
/**
* 事件分发代理
*
* @private
* @param {Object} targetEl 目标图形元素
* @param {string} eventName 事件名称
* @param {Object} event 事件对象
*/
dispatchToElement: function(targetEl, eventName, event) {
var eventHandler = 'on' + eventName;
var eventPacket = makeEventPacket(eventName, targetEl, event);
var el = targetEl;
while (el) {
el[eventHandler] && (eventPacket.cancelBubble = el[eventHandler].call(el, eventPacket));
el.trigger(eventName, eventPacket);//触发
el = el.parent;
if (eventPacket.cancelBubble) {
break;
}
}
if (!eventPacket.cancelBubble) {
// 冒泡到顶级 zrender 对象
this.trigger(eventName, eventPacket);
// 分发事件到用户自定义层
// 用户有可能在全局 click 事件中 dispose,所以需要判断下 painter 是否存在
this.painter && this.painter.eachOtherLayer(function(layer) {
if (typeof(layer[eventHandler]) == 'function') {
layer[eventHandler].call(layer, eventPacket);
}
if (layer.trigger) {
layer.trigger(eventName, eventPacket);//触发
}
});
}
}
混入Eventful(发布订阅模式事件)、Draggable(拖动事件)
util.mixin(Handler, Eventful);
util.mixin(Handler, Draggable);
对于一些事件的处理(Handler.js)
util.each(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
Handler.prototype[name] = function (event) {
// Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
var hovered = this.findHover(event.zrX, event.zrY, null);
if (name === 'mousedown') {
this._downel = hovered;
// In case click triggered before mouseup
this._upel = hovered;
}
else if (name === 'mosueup') {
this._upel = hovered;
}
else if (name === 'click') {
if (this._downel !== this._upel) {
return;
}
}
console.info("hovered:",hovered);
console.info(this);
this.dispatchToElement(hovered, name, event);
};
});
我们在其中打印了this,通过demo/demo1/demo3-chartHasHover.html的例子我们可以发现,点击的时候都会打印this,而且打印3次。
通过打印的hovered,我们可以看出来hovered就是我们点击的对象。
findHover调用的是isHover函数,在isHover函数中通过displayable(Displayable.js)的contain或者rectContain判断点在哪个元素中。
function isHover(displayable, x, y) {
if (displayable[displayable.rectHover ? 'rectContain' : 'contain'](x, y)) {
var el = displayable;
while (el) {
// If ancestor is silent or clipped by ancestor
if (el.silent || (el.clipPath && !el.clipPath.contain(x, y))) {
return false;
}
el = el.parent;
}
return true;
}
return false;
}
Displayable.js的contain或者rectContain方法都是调用rectContain方法,判断x,y是否在图形的包围盒上。
rectContain: function(x, y) {
var coord = this.transformCoordToLocal(x, y);
var rect = this.getBoundingRect();//@module zrender/core/BoundingRect
return rect.contain(coord[0], coord[1]);
}
zrender/core/BoundingRect的contain方法
contain: function(x, y) {
var rect = this;
return x >= rect.x && x <= (rect.x + rect.width) &&
y >= rect.y && y <= (rect.y + rect.height);
}
我们再来看看,在painter.js中,其实已经为每个元素生成了它的包围盒上。
var tmpRect = new BoundingRect(0, 0, 0, 0);
var viewRect = new BoundingRect(0, 0, 0, 0);
function isDisplayableCulled(el, width, height) {
tmpRect.copy(el.getBoundingRect());
if (el.transform) {
tmpRect.applyTransform(el.transform);
}
viewRect.width = width;
viewRect.height = height;
return !tmpRect.intersect(viewRect);
}
在绘制每个元素的时候,在_doPaintEl方法中调用了isDisplayableCulled。
zrender.js初步学习