在长期使用createjs的过程中,一直有这样的经历:“必须在drawXXX之前调用beginFill,否则beginFill会被忽略(是的,不会报错)”.但为什么会这样,其实也没深究。今天我很好奇想知道Graphics是如何工作的。原因是createjs.Graphics上的绘图图形API最终会转化为原生的canvas语句。所以先看看原生canvas的性能。代码1varctx=canvas.getContext("2d");ctx.rect(0,0,100,100);ctx.fillStyle="#ff0000";ctx.填充();最后的样子是:代码2varctx=canvas.getContext("2d");ctx.fillStyle="#ff0000";ctx.fill();ctx.rect(0,0,100,100);最终的表现是:为什么代码2看不到红色矩形?在photoshop上绘制一个既没有填充颜色也没有描边颜色的形状。绘制后,形状将不可见。这个原则也适用于画布。fill此API用于填充颜色。对于canvas,必须先绘制图形,然后填充(fill)或描边(stroke),图形最终会渲染到canvas上。如果在填充或描边后绘制形状,则不会渲染该形状。打个比方:先挖个坑再倒水==一坑水;先倒水再挖洞==一个洞原画布填充或描边的方法有4种,如下:.com/docs/...我在前面的描述中说过“所有Graphics上的图形绘制API最终都会转化为原生的canvas语句”,看createjs的源码确实如此。由于API太多,只能从Graphics.prototype.drawRect入手。首先,研究的源码其实就是Graphics构造函数,如下图:第二,G代表Graphics构造函数本身,p代表Graphics.prototype,如下:第三,Graphics.prototype.drawRect指向Graphics.prototype.rect,如下:四、Graphics.prototype.rect直接返回this.append,同时调用了G.Rect方法,如下:五、我们来看看G.Rect做了什么,如下:Exec出现在这里,但我不知道它的作用。但是可以看到exec把drawRect转成了原生的canvas代码!!!六、回看Graphics.prototype.append,如下:这里得到的信息是将newG.Rect(x,y,w,h)推入数组_activeInstructions。似乎没有我想要的东西,不过,我往上看它的注解如下://TODO:deprecated./***删除以支持使用自定义命令对象{{#crossLink"Graphics/append"}}{{/交叉链接}}。*@methodinject*@deprecated**//***将图形命令对象附加到图形队列。命令对象公开一个“exec”方法*,该方法接受两个参数:要操作的Context2D和传递给*{{#crossLink"Graphics/draw"}}{{/crossLink}}的任意数据对象。后者通常是调用draw的Shape实例。**此方法由图形方法(例如drawCircle)在内部使用,但也可直接用于插入*内置或自定义图形命令。例如:**//将数据附加到我们的形状,以便我们可以在绘制期间访问它:*myShape.color="red";**//追加一个Circle命令对象:*myShape.graphics.append(newcreatejs.Graphics.Circle(50,50,30));**//附加一个自定义命令对象n设置填充样式的exec方法*//基于形状的数据,然后填充圆。*myShape.graphics.append({exec:function(ctx,shape){*ctx.fillStyle=shape.color;*ctx.fill();*}});**@methodappend*@param{Object}command暴露“exec”方法的图形命令对象。*@param{boolean}cleanclean参数主要供内部使用。值为true表示命令不会生成应描边或填充的路径。*@return{Graphics}该方法被调用的Graphics实例(对链式调用很有用。)*@chainable**/这里的信息非常重要:p.append的作用是推送“命令对象(commandobject)”到“图形队列(graphicsqueue)”。graphicsqueue)”取出“命令对象”,画出这个实例的样子。第七步参考p.draw,如下:这里一目了然,队列操作是对数组进行的_执行绘制时的说明。但是第六步提到的数组是_activeInstructions,那么_instructions和_activeInstructions是什么关系呢?上图中有个叫this._updateInstructions()的可能会给我答案。第八步,检查_updateInstructions方法:从上面的代码中我们可以看出_activeInstructions是_instructions的一部分。进一步分析可以看到上面代码的下面this._fill和this._stroke。在我看来,createjs将Graphics方法分为两类三种。第一类图形绘制方法;如:rect/moveTo/lineTo等第二种渲染图形填充方式(fill);描边法(stroke);目前分析的drawRect是第一种类型。第二类需要分析。第九步分析beginFill:可以发现beginFill调用的不是this.append而是this._setFill。第十步,参考p._setFill,如下:第二种方法直接调用this._updateInstructions(true),第二种方法产生的命令不再存放在_activeInstructions数组中(其实_activeInstructions数组就是数组由一流方法生成的命令)。1615行的this.command=this._fill=fill其实很重要。回顾第八步的_updateInstructions,第二类方法内部调用了_updateInstructions,传入了一个布尔值true。它的功能是清除_activeInstructions数组(见1602行)。分析“1577~1606行”的代码可以知道,这20行代码的作用是将第二种方法产生的命令追加到_instructions数组中。这里有一个逻辑陷阱:将当前的第二个命令附加到指令数组。为什么是陷阱?回头看1614~1615行,this._fill是在调用_updateInstructions之后赋值的。这意味着下次调用_updateInstructions时,第二种方法生成的命令将附加到_instructions数组。_updateInstructions会调用哪些操作?第二类方法和p.draw。意思是:第二类方法产生的命令在队列中的位置就是下一个第二类方法所在的链位置(如果只有一个第二类方法,则在队列的末尾)链)。但是上面的结论并不能解决本文提出的知识点:“beginFill必须放在drawXXX之前,否则beginFill会被忽略”。回到1577行的判断语句:if(this._dirty&&active.length)。上面其实提到了第二种方法调用_updateInstructions方法后会清空_activeInstructions数组(即active.length===0)。此外,p.draw不会清除_activeInstructions数组,而是会将this.dirty设置为false(参见第1599行)。这意味着:Graphics链的末端是第二类方法,那么这些方法产生的命令将不会追加到_instructions数组中(即不会被执行)。如下:varrect=newcreatejs.Shape();rect.graphics.drawRect(0,0,100,100).beginFill("#ff0000").setStrokeStyle("#000000").beginStroke(4);阶段。添加孩子(矩形);上面的代码执行后是空白的。PS:第二类命令的所有方法----beginFill、beginStroke、setStrokeStyle、setStrokeDash。beginFill在功能上和form完全一样,所以只需要分析beginFill。如下:multi-graphicsinstancecreatejs.Graphics可以创建一个多图形实例,如下:varinstance=newcreatejs.Shape();instance.graphics.beginFill("#ff0000").drawRect(0,0,100,100)//Rectangle.beginFill("#ffff00").drawCircle(150,150,50)//这个圆实际上看起来像我想象中一个Shape实例只能创建一个图形,但事实是一个Shape实例可以创建多个图形。告诉我如何从原始画布绘制多个图形:varctx=canvas.getContext("2d");ctx.beginPath();ctx.rect(0,0,100,100);ctx.fillStyle="#ff0000";ctx.填充();ctx.closePath();ctx.beginPath();ctx.arc(150,150,50,150,100*Math.PI*2);ctx.fillStyle="#ffff00";ctx.填充();ctx.closePath();严格来说,一个图形在原生画布上的绘制和渲染是从beginPath()开始,到closePath()结束。实际上,beginPath()代表了上一个图的结束和下一个图的开始。所以代码可以这么简单:varctx=canvas.getContext("2d");ctx.rect(0,0,100,100);ctx.fillStyle="#ff0000";ctx.fill();ctx.beginPath();ctx.arc(150,150,50,150,100*Math.PI*2);ctx.fillStyle="#ffff00";ctx.填充();如果矩形和圆之间的beginPath()消失了,会发生什么?varctx=canvas.getContext("2d");ctx.rect(0,0,100,100);ctx.fillStyle="#ff0000";ctx.填充();ctx.arc(150,150,50,150,100*Math.PI*2);ctx.fillStyle="#ffff00";ctx.填充();在这种情况下,矩形和圆形属于同一个图形,因此填充采用最后一种颜色。回过头来看createjs.Graphics的p._updateInstructions:不难得出另一个结论:第二种方法会在它的链位置插入一个beginPath命令,用来标记上一个图形的结束和下一个图形的开始。如果以图为研究对象,不难得到Graphics绘制和渲染图的语法:第二类方法().[。第二种方法()...]。第一种方法()[.第一类方法Method()...]上式可以简单写为:第二类方法组。第一类方法组。那么如果将方法转化为对应的native命令,那么这些命令的执行顺序就是:第一种方法生成的命令->第二种方法生成的命令。它恰好可以与语言模式互换。分析本文Graphics源码后,给出的结论如下:第二个方法产生的命令在队列中的位置就是下一个第二个方法的链上位置(如果只有一个第二个方法,会beattheendofthechain))Graphicschainend是第二种方法,那么这些方法产生的命令不会追加到_instructions数组中(也就是不会被执行)第二种方法标记了一个图形的结尾在链的位置虽然下一个图形的开头有三个结论,但不方便记忆。比较有价值的应该是绘制图形的语法:“第二种方法组。第一种方法组”,但从实用价值来说,还是开头那句:beginFillmustbecalledbeforedrawXXX,否则beginFill将被忽略(是没有错误)
