首页 > 网站 > WEB开发 > 正文

ZRender源码分析4:Painter(View层)-中

2024-04-27 14:19:00
字体:
来源:转载
供稿:网友

ZRender源码分析4:Painter(View层)-中

回顾

上一篇说到:ZRender源码分析3:Painter(View层)-上,接上篇,开始Shape对象

总体理解

先回到上次的Painter的render方法

/** * 首次绘图,创建各种dom和context * 核心方法,zr.render() --> painter.render * * render和refersh的区别:render是clear所有,refresh是清除已经改变的layer * * @param {Function=} callback 绘画结束后的回调函数 */Painter.PRototype.render = function (callback) {//省略    //升序遍历,shape上的zlevel指定绘画图层的z轴层叠    this.storage.iterShape(        this._brush({ all : true }),        { normal: 'up' }    );    //省略    return this;};/** * 刷画图形 *  * @private * @param {Object} changedZlevel 需要更新的zlevel索引 */Painter.prototype._brush = function (changedZlevel) {    var ctxList = this._ctxList;    var me = this;    function updatePainter(shapeList, callback) {        me.update(shapeList, callback);    }    return function(shape) {        if ((changedZlevel.all || changedZlevel[shape.zlevel])            && !shape.invisible        ) {            var ctx = ctxList[shape.zlevel];            if (ctx) {                if (!shape.onbrush //没有onbrush                    //有onbrush并且调用执行返回false或undefined则继续粉刷                    || (shape.onbrush && !shape.onbrush(ctx, false))                ) {                    if (config.catchBrushException) {                        try {                            shape.brush(ctx, false, updatePainter);                        }                        catch(error) {                            log(                                error,                                'brush error of ' + shape.type,                                shape                            );                        }                    }                    else {                        shape.brush(ctx, false, updatePainter);                    }                }            }            else {                log(                    'can not find the specific zlevel canvas!'                );            }        }    };};

可以看到,在最核心处,便是调用了storage的遍历shape对象方法,传入的回调便是Painter._brush方法, 逻辑转入到_brush方法,这里返回一个回调,在回调中,直接调用了shape对象的brush方法,可见,最后还是要到shape对象中去了。

Shape对象

打开zrender的shape文件夹,可以看到,有很多个JS,其中,Base类是一个基类,而其他的文件都各自是一个图形类,都继承自Base类。 很明确的是,这里用的是一个模板方法,接下来,用最简单的Circle类来分析源码。先看Circle的结构。

function Circle(options) {            Base.call(this, options);}Circle.prototype = {    type: 'circle',    /**     * 创建圆形路径     * @param {Context2D} ctx Canvas 2D上下文     * @param {Object} style 样式     */    buildPath : function (ctx, style) { //省略实现    },    /**     * 返回矩形区域,用于局部刷新和文字定位     * @param {Object} style     */    getRect : function (style) { //省略实现    }};require('../tool/util').inherits(Circle, Base);

最后一行比较重要,继承了Base类,而Base类实现了brush方法,看见Circle实现的buildPath和getRect方法和type属性,应该就是覆盖了Base类的同名方法吧。 来看Base类,依旧是function Base() {} Base.prototype.baba = funciton () {},构造中先设置了一些默认值,然后用用户自定义的option进行覆盖。

 function Base( options ) {     this.id = options.id || guid();     this.zlevel = 0;     this.draggable = false;     this.clickable = false;     this.hoverable = true;     this.position = [0, 0];     this.rotation = [0, 0, 0];     this.scale = [1, 1, 0, 0];     for ( var key in options ) {         this[ key ] = options[ key ];     }     this.style = this.style || {}; }

再来看核心方法brush

/** * 画刷 *  * @param ctx       画布句柄 * @param isHighlight   是否为高亮状态 * @param updateCallback 需要异步加载资源的shape可以通过这个callback(e) *                       让painter更新视图,base.brush没用,需要的话重载brush */Base.prototype.brush = function (ctx, isHighlight) {    var style = this.style;    //比如LineShape,配置的有brushTypeOnly    if (this.brushTypeOnly) {        style.brushType = this.brushTypeOnly;    }    if (isHighlight) {        // 根据style扩展默认高亮样式        style = this.getHighlightStyle(            style,            this.highlightStyle || {},            this.brushTypeOnly        );    }    if (this.brushTypeOnly == 'stroke') {        style.strokeColor = style.strokeColor || style.color;    }    ctx.save();    //根据style设置content对象    this.setContext(ctx, style);    // 设置transform    this.updateTransform(ctx);    ctx.beginPath();    this.buildPath(ctx, style);    if (this.brushTypeOnly != 'stroke') {        ctx.closePath();    }    switch (style.brushType) {        case 'both':            ctx.fill();        case 'stroke':            style.lineWidth > 0 && ctx.stroke();            break;        default:            ctx.fill();    }    if (style.text) {        this.drawText(ctx, style, this.style);    }    ctx.restore();};
  • 1.设置brushTypeOnly,brushType有三种形式:both,stroke,fill。比如在LineShape对象中,划线是不可能fill的,只能是stroke,所以由此特殊处理
  • 2.根据当前shape的style来获取适合的highlightStyle,转入到getHighlightStyle。
    /** * 根据默认样式扩展高亮样式 *  * @param ctx Canvas 2D上下文 * @param {Object} style 默认样式 * @param {Object} highlightStyle 高亮样式 */Base.prototype.getHighlightStyle = function (style, highlightStyle, brushTypeOnly) {    var newStyle = {};    for (var k in style) {        newStyle[k] = style[k];    }    var color = require('../tool/color');    var highlightColor = color.getHighlightColor(); // rgba(255,255.0.0.5) 半透明黄色    // 根据highlightStyle扩展    if (style.brushType != 'stroke') {        // 带填充则用高亮色加粗边线        newStyle.strokeColor = highlightColor;        newStyle.lineWidth = (style.lineWidth || 1)                              + this.getHighlightZoom(); //如果是文字,就是6,如果不是文字,是2        newStyle.brushType = 'both'; //如果高亮层并且brushType为both或者fill,强制其为both    }    else {        if (brushTypeOnly != 'stroke') {            // 描边型的则用原色加工高亮            newStyle.strokeColor = highlightColor;            newStyle.lineWidth = (style.lineWidth || 1)                                  + this.getHighlightZoom();        }         else {            // 线型的则用原色加工高亮            newStyle.strokeColor = highlightStyle.strokeColor                                   || color.mix(                                         style.strokeColor,                                         color.toRGB(highlightColor)                                      );        }    }    // 可自定义覆盖默认值    for (var k in highlightStyle) {        if (typeof highlightStyle[k] != 'undefined') {            newStyle[k] = highlightStyle[k];        }    }    return newStyle;};
    • 先将默认的样式拷贝到newStyle变量中,在方法末尾,返回newStyle
    • 根据默认的样式计算出高亮的样式,如果brushType为both或者fill,将strokeColor变成半透明的黄色,根据图形类型算出lineWidth,将brushType赋值为both
    • 如果brushType为stroke,再如果brushOnly没有被设置为stroke,将strokeCOlor设置为半透明黄色,设置lineWidth
    • 如果brushType为stroke,没有设置brushOnly为stroke,就用color.mix计算出一个颜色值
    • 最后将用户自定义的highlightStyle覆盖到newStyle,返回newStyle
  • 如果brushTypeOnly为stroke,处理color的多个出处,然后就是ctx.save()与ctx.restore()之间的真正绘图了。
  • 转到setContext方法
    var STYLE_CTX_MAP = [    ['color', 'fillStyle'],    ['strokeColor', 'strokeStyle'],    ['opacity', 'globalAlpha'],    ['lineCap'],    ['lineJoin'],    ['miterLimit'],    ['lineWidth'],    ['shadowBlur'],    ['shadowColor'],    ['shadowOffsetX'],    ['shadowOffsetY']];/** * 画布通用设置 *  * @param ctx       画布句柄 * @param style     通用样式 */Base.prototype.setContext = function (ctx, style) {    for (var i = 0, len = STYLE_CTX_MAP.length; i < len; i++) {        var styleProp = STYLE_CTX_MAP[i][0];        var styleValue = style[styleProp];        var ctxProp = STYLE_CTX_MAP[i][1] || styleProp;        if (typeof styleValue != 'undefined') {            ctx[ctxProp] = styleValue;        }    }};
    在原生的context赋值样式时,都是context.fillStyle = '#aaa'; 但是经过zrender的抽象变得更加的易用,setContext就是负责原生canvasAPI与zrender.shape.style的转换, 其实有变化的就只有fillStyle,strokeStyle,globalAlpha。分别用style.color,style.strokeColor,opacity进行替换,不过这些原生API的属性名确实不那么平易近人。
  • 关于变形,暂时跳过
  • 开始beginPath,然后调用Base.buildPath,发现Base中没有buildPath的实现,上面说了嘛,在子类实现了,模板方法。下面举例 进行buildPath的分析
    // shape/Circle.js/** * 创建圆形路径 * @param {Context2D} ctx Canvas 2D上下文 * @param {Object} style 样式 */buildPath : function (ctx, style) {    ctx.arc(style.x, style.y, style.r, 0, Math.PI * 2, true);    return;},//shape/Rectangle/** * 创建矩形路径 * @param {Context2D} ctx Canvas 2D上下文 * @param {Object} style 样式 */buildPath : function(ctx, style) {    if(!style.radius) {        ctx.moveTo(style.x, style.y);        ctx.lineTo(style.x + style.width, style.y
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表