ZRender源碼分析3:Painter(View層)-上

來源:互聯網
上載者:User

標籤:style   blog   http   color   io   os   使用   ar   for   

回顧

上一篇說到:ZRender源碼分析2:Storage(Model層),這次咱看來看看Painter-View層

總體理解

Painter這個類主要負責MVC中的V(View)層,負責將Storage中的shape對象繪製到canvas中,包括了:更新、渲染、變化大小、匯出、修改等操作。
Painter這個類還是很明顯的建構函式,然後把方法賦值到Painter.prototype上,無新奇之處,下面為範例程式碼。只有在Painter.js末尾有一個內部的createDom函數,很明顯,傳入id,type(tagName),painter(用來確定寬高)來建立一個新的dom元素,並且這個dom元素的寬高與painter的相同,tagname為type,絕對位置,擁有一個自訂屬性key為ata-zr-dom-id,value為id。

function Painter(root,stroage) {this.root = xxxx;}Painter.prototype.render = function () {};Painter.prototype.refresh = function () {};Painter.prototype.update = function () {};Painter.prototype.clear = function () {};..... /** * 建立dom *  * @inner * @param {string} id dom id 待用 * @param {string} type dom type,such as canvas, div etc. * @param {Painter} painter painter instance */function createDom(id, type, painter) {    var newDom = document.createElement(type);    var width = painter._width;    var height = painter._height;    // 沒append呢,請原諒我這樣寫,清晰~    newDom.style.position = ‘absolute‘;    newDom.style.left = 0;    newDom.style.top = 0;    newDom.style.width = width + ‘px‘;    newDom.style.height = height + ‘px‘;    newDom.setAttribute(‘width‘, width * devicePixelRatio);    newDom.setAttribute(‘height‘, height * devicePixelRatio);    // id不作為索引用,避免可能造成的重名,定義為私人屬性    newDom.setAttribute(‘data-zr-dom-id‘, id);    return newDom;}
建構函式
/** * 繪圖類 (V) *  * @param {HTMLElement} root 繪圖區域 * @param {storage} storage Storage執行個體 */function Painter(root, storage) {    this.root = root;    this.storage = storage;    root.innerHTML = ‘‘;    this._width = this._getWidth(); // 寬,緩衝記錄    this._height = this._getHeight(); // 高,緩衝記錄    var domRoot = document.createElement(‘div‘);    this._domRoot = domRoot;    //domRoot.onselectstart = returnFalse; // 避免頁面選中的尷尬    domRoot.style.position = ‘relative‘;    domRoot.style.overflow = ‘hidden‘;    domRoot.style.width = this._width + ‘px‘;    domRoot.style.height = this._height + ‘px‘;    root.appendChild(domRoot);    this._domList = {};       //canvas dom元素    this._ctxList = {};       //canvas 2D context對象,與domList對應    this._domListBack = {};    this._ctxListBack = {};           this._zLevelConfig = {}; // 每個zLevel 的配置,@config clearColor    this._maxZlevel = storage.getMaxZlevel(); //最大zlevel,緩衝記錄    // this._loadingTimer     this._loadingEffect = new BaseLoadingEffect({});    this.shapeToImage = this._createShapeToImageProcessor();    // 建立各層canvas    // 背景    this._domList.bg = createDom(‘bg‘, ‘div‘, this);    domRoot.appendChild(this._domList.bg);    var canvasElem;    var canvasCtx;    /**     * 每一個level,就是一個canvas     *     * DOM結構     * root     *   ->domRoot     *       ->canvas level1     *       ->canvas level2     *       ->canvas level3     *       ->canvas hover_level     *     * _domList儲存所有的DOM引用     * {     *      1:CanvasHTMLElement     *      2:CanvasHTMLElement     *      3:CanvasHTMLElement     *      hover:CanvasHTMLElement     * }     *     * ctxList儲存所有的canvas.getContext(‘2d‘)引用     * {     *      1:CanvasContext     *      2:CanvasContext     *      3:CanvasContext     *      hover:CanvasContext     * }     */    // 實體    for (var i = 0; i <= this._maxZlevel; i++) {        canvasElem = createDom(i, ‘canvas‘, this);        domRoot.appendChild(canvasElem);        this._domList[i] = canvasElem;        vmlCanvasManager && vmlCanvasManager.initElement(canvasElem); // excanvas method        this._ctxList[i] = canvasCtx = canvasElem.getContext(‘2d‘);        if (devicePixelRatio != 1) {             canvasCtx.scale(devicePixelRatio, devicePixelRatio);        }    }    // 高亮    canvasElem = createDom(‘hover‘, ‘canvas‘, this);    canvasElem.id = ‘_zrender_hover_‘;    domRoot.appendChild(canvasElem);    this._domList.hover = canvasElem;    vmlCanvasManager && vmlCanvasManager.initElement(canvasElem); // excanvas method    this._domList.hover.onselectstart = returnFalse;    this._ctxList.hover = canvasCtx = canvasElem.getContext(‘2d‘);    if (devicePixelRatio != 1) { //處理視網膜        canvasCtx.scale(devicePixelRatio, devicePixelRatio);    }}Painter.prototype._getWidth = function() {    var root = this.root;    var stl = root.currentStyle              || document.defaultView.getComputedStyle(root);    return ((root.clientWidth || parseInt(stl.width, 10))            - parseInt(stl.paddingLeft, 10) // 請原諒我這比較粗暴            - parseInt(stl.paddingRight, 10)).toFixed(0) - 0;    /**     * 這裡用實際的width減去了左右的padding     * 為什麼不考慮將這兩個方法就行重載?     */};Painter.prototype._getHeight = function () {    var root = this.root;    var stl = root.currentStyle              || document.defaultView.getComputedStyle(root);    return ((root.clientHeight || parseInt(stl.height, 10))            - parseInt(stl.paddingTop, 10) // 請原諒我這比較粗暴            - parseInt(stl.paddingBottom, 10)).toFixed(0) - 0;};
    • 1.首先看_getWidth和_getHeight兩個方法,這是擷取當前root元素的實際寬度和高度值,詳情請看這裡 擷取元素CSS值之getComputedStyle方法熟悉
    • 2.看構造,運行上篇樣本,開啟Chrome控制台的Element Tab,可以看到如下HTML結構:
      再看painter在記憶體中:

    然後咱們參照上面兩個圖,分析流程:
    • 先在指定的dom(box)元素下插入一個domRoot(跟box寬高一樣,絕對位置)
    • 在domRoot上插入一個背景div,儲存到this._domList.bg變數中
    • 遍曆從storage中獲得的_maxZlevel,每層對應一個canvas元素,插入到domRoot中,儲存到this.domList[遍曆序號]中,並調用每個canvas元素的getContext(‘2d‘)獲得context,儲存到this._ctxList[遍曆序號]中
    • 最後處理高亮層,依舊是插入到domRoot元素中,將canvas引用和context引用存入到domList和ctxList中,不過標識都變成了hover
    • 關於視網膜螢幕請看: http://www.myexception.cn/mobile/1489709.html 與 http://www.zhangxinxu.com/wordpress/2012/10/new-pad-retina-devicepixelratio-css-page/
    • 關於vmlCanvasManager請看 IE下使用excanvas.js的注意事項
  • 3.關於載入動畫loadingEffect,暫時跳過
  • 4.關於shapeToImage,意思是將非imageShape對象轉換為ImageShape對象
    //////////////以下為zrender.js中代碼/////////////////////** * 將常規shape轉成image shape */ZRender.prototype.shapeToImage = function(e, width, height) {    var id = guid();    return this.painter.shapeToImage(id, e, width, height);};//////////////以下為Painter.js中代碼////////////////////Painter.prototype._createShapeToImageProcessor = function () {    if (vmlCanvasManager) {        return doNothing;    }    var painter = this;    var canvas = document.createElement(‘canvas‘);    var ctx = canvas.getContext(‘2d‘);    var devicePixelRatio = window.devicePixelRatio || 1;        return function (id, e, width, height) {        return painter._shapeToImage(            id, e, width, height,            canvas, ctx, devicePixelRatio        );    };};Painter.prototype._shapeToImage = function (    id, shape, width, height,    canvas, ctx, devicePixelRatio) {    canvas.style.width = width + ‘px‘;    canvas.style.height = height + ‘px‘;    canvas.setAttribute(‘width‘, width * devicePixelRatio);    canvas.setAttribute(‘height‘, height * devicePixelRatio);    ctx.clearRect(0, 0, width * devicePixelRatio, height * devicePixelRatio);    var shapeTransform = {        position : shape.position,        rotation : shape.rotation,        scale : shape.scale    };    shape.position = [0, 0, 0];    shape.rotation = 0;    shape.scale = [1, 1];    if (shape) {        shape.brush(ctx, false);    }    var ImageShape = require( ‘./shape/Image‘ );    var imgShape = new ImageShape({        id : id,        style : {            x : 0,            y : 0,            // TODO 直接使用canvas而不是通過base64            image : canvas.toDataURL()        }    });    if (shapeTransform.position != null) {        imgShape.position = shape.position = shapeTransform.position;    }    if (shapeTransform.rotation != null) {        imgShape.rotation = shape.rotation = shapeTransform.rotation;    }    if (shapeTransform.scale != null) {        imgShape.scale = shape.scale = shapeTransform.scale;    }    return imgShape;};
    • 從zrender.js中調用過來,用了兩層閉包,有點繞,大家自行腦補,總之,最後zrender.shapeToImage(xxx)返回的是一個ImageShape對象
    • 在painter的建構函式中有_createShapeToImageProcessor的調用,直接指向了this.shapeToImage,這說明_shapeToImage只是一個內部方法
    • 在_createShapeToImageProcessor中,我們發現,如果用的是excanvas(IE678),那麼不支援這個特性,return掉(這是在API沒有公開這個介面的原因?)
    • 如果不是excanvas,自行建立一個canvas元素,擷取其context對象,然後傳給_shapeToImage,饒了半天,最後Painter._shapeToImage才是苦力工啊
    • 在_shapeToImage中,首先將canvas的寬高設定成指定的寬高,然後清除畫布,儲存變形參數,再將變形參數重設,調用 shape的brush方法進行繪製,此時,已經完成了新canvas的建立,然後再畫上指定的shape
    • 建立一個ImageShape,將image設定為以前建立的canvas.toDataURL() 關於canvas與Image互換,請看:http://www.jb51.net/html5/97104.html
    • 最後把之前shape的變形參數設定到ImageShape上
    • 既然API中不公開這個介面,其他地方也沒調用,作者是個啥意圖呢?
關於完美繼承
/** * 構造類繼承關係 *  * @param {Function} clazz 源類 * @param {Function} baseClazz 基類 */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.constructor = clazz;}

因為接下來的講解之中,在loadingEffect和Shape對象中,都會有JS繼承的出現,zrender/tool/util.js中有一個inherits的方法,實現了完美繼承。 有興趣的同學們可以看看下面兩個,我就不詳細的說了。

  • http://blog.csdn.net/justoneroad/article/details/7327805
  • http://my.oschina.net/antianlu/blog/228267
結束

因為Painter的內容牽扯較多,關於Shape對象不詳細說道說道又無法進行,說以下篇咱們看看Shape到底是怎麼組織的,等下下篇,再從來Painter類

ZRender源碼分析3:Painter(View層)-上

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.