上一篇说到: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对象中去了。
打开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();};
/** * 根据默认样式扩展高亮样式 * * @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;};
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的属性名确实不那么平易近人。
// 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
新闻热点
疑难解答