摘要:需要分析第二類。這意味著第二類方法生成的命令會在下次調(diào)用是被追加到數(shù)組上。另外不會清空數(shù)組,卻會把置為見行。功能上在形式上完全一樣,所以只需要分析即可。正好與語式左右互換。
在長期使用 createjs 的過程中,我一直有這樣一個經(jīng)驗:「beginFill 必須在 drawXXX 之前調(diào)用,否則 beginFill 會被忽略(是的不報錯)」。
但是為什么會這樣,其實并沒有去深究它。今天很想知道 Graphics 是怎么工作的。
createjs.Graphics上的繪制圖形API最終都會轉(zhuǎn)換成原生canvas語句。所以先看一下原生 canvas 的表現(xiàn)。
代碼一
var ctx = canvas.getContext("2d");
ctx.rect(0, 0, 100, 100); ctx.fillStyle = "#ff0000"; ctx.fill();
最終的樣子是:
代碼二
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#ff0000"; ctx.fill(); ctx.rect(0, 0, 100, 100);
最終的樣子是:
為什么代碼二看不到紅色的矩形?
在 photoshop 上畫一個既沒有填充顏色又沒有描邊顏色的圖形,畫完之后這個圖形是看不到的,這個道理也同樣適用于 canvas。fill 這個API就是用于填充顏色。
對于 canvas 來說,要先繪制圖形再進(jìn)行填充(fill)或描邊(stroke),圖形最終才會被渲染到畫布上。如果先填充或描邊后再繪制圖形,那么圖形不會被渲染。
打個比喻:
先挖坑再倒水 == 一坑水;
先倒水再挖坑 == 一個坑
原生 canvas 的填充或描邊方法共有4個,如下:
fill
stroke
fillRect
strokeRect
分析 createjs.Graphics 的源碼createjs.Graphics 的源碼地址:http://www.createjs.com/docs/...
我在之前的描述中說過「所有的Graphics上的繪制圖形API最終都會轉(zhuǎn)換成原生canvas語句」,看 createjs 的源碼也確實如此。由于 API 太多,只能先從 Graphics.prototype.drawRect 切入。
首先研究的對源碼其實是 Graphics 構(gòu)造函數(shù),如下圖:
其次, G 表示 Graphics 構(gòu)造函數(shù)本身,p 代表 Graphics.prototype,如下:
第三,Graphics.prototype.drawRect 指向了 Graphics.prototype.rect,如下:
第四,Graphics.prototype.rect直接返回了 this.append 并同時調(diào)用了 G.Rect 方法,如下 :
第五,先看一下 G.Rect 做了什么,如下:
這里出現(xiàn)了 exec 不知道是做什么的,不過可以看到 exec 把 drawRect 轉(zhuǎn)換成原生的 canvas 代碼了!!!
第六,回頭看 Graphics.prototype.append ,如下:
這里得到的信息就是把 new G.Rect(x, y, w, h) push 到數(shù)組 _activeInstructions中。似乎沒有我想要的東西,不過,我往上看它的注釋如下:
// TODO: deprecated. /** * Removed in favour of using custom command objects with {{#crossLink "Graphics/append"}}{{/crossLink}}. * @method inject * @deprecated **/ /** * Appends a graphics command object to the graphics queue. Command objects expose an "exec" method * that accepts two parameters: the Context2D to operate on, and an arbitrary data object passed into * {{#crossLink "Graphics/draw"}}{{/crossLink}}. The latter will usually be the Shape instance that called draw. * * This method is used internally by Graphics methods, such as drawCircle, but can also be used directly to insert * built-in or custom graphics commands. For example: * * // attach data to our shape, so we can access it during the draw: * myShape.color = "red"; * * // append a Circle command object: * myShape.graphics.append(new createjs.Graphics.Circle(50, 50, 30)); * * // append a custom command object with an exec method that sets the fill style * // based on the shape"s data, and then fills the circle. * myShape.graphics.append({exec:function(ctx, shape) { * ctx.fillStyle = shape.color; * ctx.fill(); * }}); * * @method append * @param {Object} command A graphics command object exposing an "exec" method. * @param {boolean} clean The clean param is primarily for internal use. A value of true indicates that a command does not generate a path that should be stroked or filled. * @return {Graphics} The Graphics instance the method is called on (useful for chaining calls.) * @chainable **/
這里的信息太重要了:p.append 的作用是把「命令對象(command object)」推到「圖像隊列(graphics queue)」中。當(dāng)「圖形實例(Shape instance)」調(diào)用 draw 方法時,會從「圖像隊列(graphics queue)」取出「命令對象」,并把繪制出這個實例的樣子。
第七步,查閱 p.draw,如下:
這里一目了然,在執(zhí)行 draw 時是對數(shù)組 _instructions 做出隊列操作。但是,第六步提到的數(shù)組是_activeInstructions,那么 _instructions 與 _activeInstructions 是什么關(guān)系呢?上圖有一個叫 this._updateInstructions() 或許可以給我答案。
第八步,查閱 _updateInstructions 方法:
從上圖代碼可知:_activeInstructions 是 _instructions 的一部分。再深入分析可以看到上圖代碼接下來的 this._fill 與 this._stroke。
在我看來 createjs 把Graphics 的方法分成兩類三種。
第一類 | 繪制圖形 | 繪制方法;如: rect/moveTo/lineTo 等 |
---|---|---|
第二類 | 渲染圖形 |
填充方法(fill); 描邊方法(stroke); |
當(dāng)前分析的 drawRect 就是第一類。需要分析第二類。
第九步,分析 beginFill:
可以發(fā)現(xiàn),beginFill 不調(diào)用 this.append 而是 this._setFill 了。
第十步,查閱 p._setFill,如下:
第二類方法直接就調(diào)用了 this._updateInstructions(true) 了,而且第二類方法生成的命令也不再是存入 _activeInstructions數(shù)組中了(其實 _activeInstructions 數(shù)組就是第一類方法生成的命令的數(shù)組)。
1615 行的 this.command = this._fill = fill,其實很重要。回頭看第八步的 _updateInstructions ,第二類方法內(nèi)部調(diào)用 _updateInstructions 并傳入 boolean 值true,它的作用是清空 _activeInstructions 數(shù)組(見 1602 行)。
分析「1577行~1606行」的代碼可以知道這20行的代碼的作用是把第二類方法生成的命令追加到 _instructions 數(shù)組上。這里有一個邏輯陷井:把當(dāng)前的第二命令追加到 instructions 數(shù)組上。
為什么是個陷井呢?
回頭看 1614~1615 行,this._fill 在 調(diào)用 _updateInstructions 后被賦值。這意味著第二類方法生成的命令會在下次調(diào)用 _updateInstructions 是被追加到 _instructions 數(shù)組上。
哪些操作會調(diào)用到 _updateInstructions?
第二類方法 與 p.draw。
這意味著:第二類方法生成的命令位于隊列的位置是下一個第二類方法所在的鏈?zhǔn)轿恢?如果只有一個第二類方法則在鏈?zhǔn)阶詈?。
但是上面的結(jié)論并不能解決本文本拋出的知識點:「beginFill必須放在 drawXXX 之前,否則beginFill會被忽略」。
回到1577行的判斷語句:if (this._dirty && active.length) 。上面其實提到了第二類方法調(diào)用 _updateInstructions 方法后會把 _activeInstructions 數(shù)組清空( 即active.length === 0)。另外 p.draw 不會清空 _activeInstructions 數(shù)組,卻會把 this.dirty 置為 false(見行:1599)。
這意味著:Graphics的鏈?zhǔn)侥┪捕际堑诙惙椒ǎ敲催@些方法生成的命令不會被追加到 _instructions 數(shù)組上(即不會被執(zhí)行)。如下:
var rect = new createjs.Shape();
rect.graphics.drawRect(0, 0, 100, 100).beginFill("#ff0000").setStrokeStyle("#000000").beginStroke(4);
stage.addChild(rect);
上面的代碼執(zhí)行后是空白。
PS:第二類命令所有的方法 ---- beginFill, beginStroke, setStrokeStyle, setStrokeDash。
功能上 beginFill 在形式上完全一樣,所以只需要分析 beginFill 即可。如下:
createjs.Graphics 是可以創(chuàng)建一個多圖形實例的,如下:
var instance = new createjs.Shape();
instance.graphics
.beginFill("#ff0000").drawRect(0, 0, 100, 100) // 矩形 .beginFill("#ffff00").drawCircle(150, 150, 50) // 圓形
其實我想象中的樣子是一個 Shape 實例只能創(chuàng)建一個圖形,但事實是一個 Shape 實例是可以創(chuàng)建多個圖形的。從原生 canvas 說一下多圖形是怎么繪制的:
var ctx = canvas.getContext("2d");
ctx.beginPath(); ctx.rect(0, 0, 100, 100); ctx.fillStyle = "#ff0000"; ctx.fill(); ctx.closePath(); ctx.beginPath(); ctx.arc(150, 150, 50, 150, 100 * Math.PI * 2); ctx.fillStyle = "#ffff00"; ctx.fill(); ctx.closePath();
嚴(yán)格上說,原生 canvas 的一個圖形的繪制與渲染由 beginPath() 開始,再由 closePath() 結(jié)束。
實際上,beginPath() 代表上一個圖形的結(jié)束和下一個圖形的開始。
所以代碼可以簡單為:
var ctx = 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.fill();
如果矩形與圓形中間的 beginPath() 沒有了,會怎么樣?
var ctx = canvas.getContext("2d");
ctx.rect(0, 0, 100, 100); ctx.fillStyle = "#ff0000"; ctx.fill(); ctx.arc(150, 150, 50, 150, 100 * Math.PI * 2); ctx.fillStyle = "#ffff00"; ctx.fill();
這種情況,矩形和圓形同屬于一個圖形,所以 fill 填充取的是最后一次的顏色。
回頭看 createjs.Graphics的 p._updateInstructions:
很容易得出另一個結(jié)論:第二類方法會在其鏈?zhǔn)缴纤谖恢貌迦隻eginPath的命令以標(biāo)記上一個圖形結(jié)束和下一個圖形開始。
如果以一個圖形為研究對象不難得出Graphics 繪制渲染一個圖形的語式:第二類方法().[.第二類方法()...].第一類方法()[.第一類方法()...]
上面的語式可以簡單地寫成: 第二類方法組.第一類方法組。
然后如果把方法轉(zhuǎn)化為對應(yīng)的原生命令,那么這些命令的執(zhí)行順序是:第一類方法生成的命令 -> 第二類方法生成的命令。正好與語式左右互換。
本文對 Graphics 源碼解析后,給出的結(jié)論如下:
第二類方法生成的命令位于隊列的位置是下一個第二類方法所在的鏈?zhǔn)轿恢?如果只有一個第二類方法則在鏈?zhǔn)阶詈?
Graphics的鏈?zhǔn)侥┪捕际堑诙惙椒ǎ敲催@些方法生成的命令不會被追加到 _instructions 數(shù)組上(即不會被執(zhí)行)
第二類方法在鏈?zhǔn)降奈恢脴?biāo)志上一個圖形的結(jié)束和下一個圖形的開始
雖然有三個結(jié)論,不過不便被記憶。
更有價值的應(yīng)該是繪制圖形的語式:「第二類方法組.第一類方法組」
但論實用價值還是開頭的那句話:beginFill 必須在 drawXXX 之前調(diào)用,否則 beginFill 會被忽略(是的不報錯)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/88379.html
摘要:昨天看了老外的視頻教程,介紹了做大大節(jié)約了開發(fā)的成本,老外用原生的和各實現(xiàn)了一遍方塊旋轉(zhuǎn)動畫。 昨天看了老外的視頻教程,介紹了easeljs做canvas大大節(jié)約了開發(fā)的成本,老外用原生的canvas和easeljs 各實現(xiàn)了一遍方塊旋轉(zhuǎn)動畫。 這時的我感覺很驚訝,原來動畫做起來并不是我想得這么復(fù)雜,于是自己用模擬easeljs也做了一個動畫旋轉(zhuǎn),感覺棒棒噠~ ...
摘要:以后所有的文章都會第一時間更新到這里,然后同步到其他平臺。獲取畫布的寬和高,后面計算使用定義靜態(tài)資源人物動作雪碧圖天空地面遠(yuǎn)山近山創(chuàng)建資源加載隊列用還是用標(biāo)簽來加載如果是的時候,就用標(biāo)簽來加載,如果不能用標(biāo)簽的話,就用來加載。 寫在前面 首先,還是謝謝大家的支持,謝謝!記得在之前的文章中我說過自己算是一個半文藝程序員,也一直想著寫一寫技術(shù)性和其他偏文學(xué)性的文章。雖然自己的底子沒有多么優(yōu)...
摘要:以后所有的文章都會第一時間更新到這里,然后同步到其他平臺。獲取畫布的寬和高,后面計算使用定義靜態(tài)資源人物動作雪碧圖天空地面遠(yuǎn)山近山創(chuàng)建資源加載隊列用還是用標(biāo)簽來加載如果是的時候,就用標(biāo)簽來加載,如果不能用標(biāo)簽的話,就用來加載。 寫在前面 首先,還是謝謝大家的支持,謝謝!記得在之前的文章中我說過自己算是一個半文藝程序員,也一直想著寫一寫技術(shù)性和其他偏文學(xué)性的文章。雖然自己的底子沒有多么優(yōu)...
閱讀 2788·2023-04-25 14:41
閱讀 2383·2021-11-23 09:51
閱讀 3678·2021-11-17 17:08
閱讀 1674·2021-10-18 13:31
閱讀 5544·2021-09-22 15:27
閱讀 917·2019-08-30 15:54
閱讀 2226·2019-08-30 13:16
閱讀 735·2019-08-29 17:04