ECharts 3.0底層zrender 3.x源碼分析3-Handler(C層)canvas事件

這一篇,介紹下Handler處理機制。

Handler負責事件處理,包括’click’, ‘dblclick’, ‘mousewheel’, ‘mouseout’, ‘mouseup’, ‘mousedown’, ‘mousemove’, ‘contextmenu’等。我們知道canvas API沒有提供監聽每個元素的機制,這就需要一些處理。處理的思路是:監聽事件的作用座標(如點擊時候的座標),判斷在哪個繪製元素的範圍中,如果在某個元素中,這個元素就監聽該事件。

一些demo和沒有在博客中介紹的源碼請進我的github倉庫。

https://github.com/zrysmt/echarts3/tree/master/zrender

1.Handle.js整體
同樣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);

2.canvas上元素的監聽事件
對於一些事件的處理(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。

參考閱讀: 
- canvas-mdn教程 
- canvas基本的動畫-mdn
————————————————
版權聲明:本文爲CSDN博主「TechFE」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/future_todo/article/details/54341458

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章