本部分重點講解了 Canvas 中心點的內容,在 Canvas 中所有內容的繪製都是基於上下文變換後的新座標系中心點來完成的,對於座標系中心點的理解是正確完成內容繪製的前提。同時,本章節也將重點講述 Canvas 繪圖狀態相關的兩個方法 save 和 restore。
1。Canvas 與 CSS3 座標變換
1。1 CSS3 matrix 2D 矩陣和 Canvas transform 2D 矩陣
2D 矩陣指的是元素在 2D 平面內發生諸如縮放、平移、旋轉、拉伸四種變化,在 CSS3 中對應 4 個方法分別是
scale()
、
translate()
、*rotate()*和
skew()
,這 4 個方法是 CSS3 矩陣 matrix 的快捷方式,其本質都是由
matrix()
實現的。
類似地,在 Canvas 中與 CSS3 對應的 3 個方法分別是
scale()
、
translate()
、
rotate()
,而 Canvas 物件沒有
skew
方法,CSS3 中的矩陣和 Canvas 矩陣原理是相通的。matrix 方法有六個引數 matrix(a, b, c, d, x, y),六個引數預設值是:
matrix(1, 0, 0, 1, 0, 0)
這六個引數分別控制不同的變換
a 水平縮放
b 水平拉伸
c 垂直拉伸
d 垂直縮放
x 水平位移
y 垂直位移
關於 matrix 的各種變換在後面章節會有更加詳細的說明,在這裡只需知道 Canvas 和 CSS3 中矩陣變換的規則是一致的。
1。2 Canvas 與 CSS3 座標變換中心點差異
1。1 部分講過,在 Canvas 中也存在 CSS3 2D 變換的功能,如 translate()、rotate()、scale() 等。雖然兩者看起來差不多,但是 CSS3 中元素變換中心點都是針對 DOM 元素,而在 Canvas 中並非針對 DOM 元素的變換,而是虛擬畫布區域。
預設情況下,Canvas 中心點是左上角,即座標(0,0)。就像可以透過 transform-origin 來改變 CSS3 元素變換的中心點一樣,Canvas 也可以改變預設中心點,只不過需要透過 translate()方法
平移內部的 2D 繪圖環境
。
#div2 { transform: rotate(45deg); transform-origin: 20% 40%;}
總之,對於中心點而言,
CSS3 是針對元素本身,即 DOM 元素,而 Canvas 針對的是整個 Canvas 繪圖環境,即虛擬畫布區域
2。Canvas 上下文變換
2。1 Canvas 上下文變換基礎
首先來看下在不改變中心點情況下,Canvas 旋轉前後的變化:
<!—— 真實的畫布就是200x200 ——>
var canvas = document。getElementById(‘canvas’);ctx = canvas。getContext(‘2d’);ctx。save();ctx。fillStyle = ‘black’;ctx。fillRect(20, 20, 100, 100);// 旋轉前繪製ctx。rotate((Math。PI / 180) * 30);ctx。fillStyle = ‘blue’;ctx。fillRect(20, 20, 100, 100);// 旋轉後繪製ctx。restore();
由上面的程式碼可以看到,在旋轉畫布前,我們在座標(20,20)處繪製了一個 100*100 的黑色矩形,而在旋轉之後,又在座標(20,20)處繪製了一個 100*100 的藍色矩形。最後得到的效果如下:
需要注意的是,Canvas 旋轉前繪製的元素沒有旋轉效果,而這種旋轉效果只會出現在 Canvas 旋轉後繪製的元素。因此,如果要對 Canvas 裡的某些圖形進行旋轉處理,就必須在
繪圖環境旋轉後
再進行繪製。
那麼如果將 Canvas 旋轉 180° 後再進行繪製,最後結果如何呢?
window。onload = function () { var c = document。getElementById(‘myCanvas’); var ctx = c。getContext(‘2d’); ctx。rotate((Math。PI / 180) * 180); var img = document。getElementById(‘tulip’); ctx。drawImage(img, 10, 10);};
執行完上述程式碼,你會發現什麼也看不到。因為 Canvas 的中心點是左上角(0,0),當將 Canvas 繪圖環境旋轉 180 弧度後,你會發現圖形已經在 Canvas 可視區域外,這顯然是不可行的。那需要如何做呢?
2。2 Canvas 上下文變換原理
下面將以圖解的方式進一步講述 Canvas 上下文變換的原理,透過分析你也能進一步深入理解上文旋轉 180° 的例子,即繪製的元素為啥會莫名消失。
下面分步對該圖進行講解:
第一步:不做任何原點移動的繪製
var c = document。getElementById(‘myCanvas’);var ctx = c。getContext(‘2d’);var img = document。getElementById(‘tulip’);ctx。drawImage(img, 10, 10);
此時透過 X,Y 軸指定了繪製的方向,整個圖以 O(0,0)為中心進行繪製。
第二步:移動了繪製原點
ctx。translate(w /
2
, h /
2
);
繪製原點移動到 O`(1/2w,1/2h),而 X`和 Y`指定了最新繪製的方向。此時需要注意的是:整個 Canvas 在頁面中展示區域依然是 w,h 指定的位置,只是繪圖的原點發生了改變而已。所以,在 Canvas 中灰色區域是整個 w,h 指定的唯一有影象的區域,而其他區域都是空的,因為 Context 壓根就沒有在這些位置進行繪製。
第三步:上下文進行旋轉
ctx。translate(w / 2, h / 2);ctx。rotate(Math。PI);ctx。drawImage(img, 0, 0);
透過這一步,X 軸,Y 軸的方向方向再次發生了變化,分別為 X``和 Y``,但是原點依然對應於 O`(1/2w,1/2h),因為 Context 沒有做類似 translate 方法來改變中心點位置。這一步需要注意的是繪製的方向,由 X``和 Y``的指向來看,此時的繪圖方向已經轉化為向上和向右繪製。和第二步分析一致,黃色區域是唯一能看到影象的區域。到這一步,你應該明白了,現在依然沒有實現元素在指定位置的 180° 旋轉。請看下例:
window。onload = function () { var c = document。getElementById(‘myCanvas’); var ctx = c。getContext(‘2d’); ctx。translate(500 / 2, 300 / 2); ctx。rotate(Math。PI); var img = document。getElementById(‘tulip’); ctx。drawImage(img, 10, 10);};
原圖為:
程式碼執行後的效果為:
圖片具體表現可以透過上面的分析看出來,很顯然現在繪製出來的圖片只是原圖的一部分,而不是完整的圖片。究其原因主要是當圖片尺寸超過 0。5w,0。5h 的時候,黃色區域沒法完全容納整張圖片的繪製。
而造成該問題的本質原因在於上下文移動的 0.5w,0.5h 距離
。下面部分講解具體的解決方法:
2。3 Canvas 上元素旋轉 180° 的方法
透過上面 2。2 的分析不難看出具體的解決方法。第一種途徑是在 rotate 後繼續改變繪圖環境的中心點,將中心點平移到作圖區域(-w/2,-h/2)。之所以為負數,是因為 X``和 Y``指定了新的座標方向與原點要移動的位置相反。程式碼有:
ctx。translate(w / 2, h / 2);ctx。rotate(Math。PI);ctx。translate(-w / 2, -h / 2);ctx。drawImage(img, 0, 0);
最終效果如下:
第二種途徑是改變繪製圖片的座標,將圖片繪製到(-w/2,-h/2),程式碼有:
ctx。translate(w / 2, h / 2);ctx。rotate(Math。PI);ctx。drawImage(img, -w / 2, -h / 2);
具體效果與上圖相同。
3。Canvas 上下文狀態儲存
在上面第 2 部分講過上下文變換設定只會影響到之後的內容繪製,接下來重點講述下 Canvas 上下文狀態相關的兩個主要方法, save()和
restore()
。
3。1 save()和 restore()方法詳解
Canvas Context 維持著繪製狀態的堆疊,繪製狀態主要包括以下幾個維度:
1。上下文矩陣變換:例如:平移 translate(),縮放 scale(),以及旋轉 rotate()等2。剪下區域: clip()3。特殊屬性值設定: strokeStyle,fillStyle,globalAlpha,lineWidth,lineCap,lineJoin,miterLimit,shadowOffsetX,shadowOffsetY,shadowBlur,shadowColor,globalCompositeOperation,font,textAlign,textBaseline等
需要注意的是:當前繪製路徑、畫布內容本身不屬於繪圖狀態。繪製路徑是持久的,只能使用 *beginPath()*方法重置,而畫布內容是畫布的屬性,而非上下文。save() 和 restore() 的出現提供了用來操作繪製狀態的快捷方法。
1。Context。save() 方法將當前繪製狀態壓入堆疊2。Context。restore() 彈出堆疊上的狀態,將上下文恢復到該狀態。
3。2 save()和 restore()方法示例
當前示例的完整程式碼可以檢視這裡,將程式碼複製在任何編輯器中以。html 為檔案字尾,用瀏覽器開啟即可看到完整示例效果,下面對核心程式碼進行說明。
第一步: 首先設定了 Canvas 繪製的 fillStyle、shadow 屬性,然後在(0,0)處繪製了一個 15x150 的矩形。接著呼叫了 *ctx。save()*方法將當前繪製狀態壓入堆疊。
ctx。fillStyle = ‘#FA6900’;ctx。shadowOffsetX = 5;ctx。shadowOffsetY = 5;ctx。shadowBlur = 4;ctx。shadowColor = ‘rgba(204, 204, 204, 0。5)’;ctx。fillRect(0, 0, 15, 150);ctx。save();
當前畫布及堆疊的狀態如下:
第二步:重新設定 fillStyle、shadow 屬性,然後在座標(30,0)處繪製一個 30x150 的矩陣。接著呼叫 *ctx。save()*方法將當前繪製狀態壓入堆疊。
ctx。fillStyle = ‘#E0E4CD’;ctx。shadowOffsetX = 10;ctx。shadowOffsetY = 10;ctx。shadowBlur = 4;ctx。shadowColor = ‘rgba(204, 204, 204, 0。5)’;ctx。fillRect(30, 0, 30, 150);ctx。save();
當前畫布以及堆疊的完整狀態如下:
第三步:再次重新設定 fillStyle、shadow 屬性,然後在座標(90,0)處繪製一個 45x150 的矩陣。接著呼叫 *ctx。save()*方法將當前繪製狀態壓入堆疊。
ctx。fillStyle = ‘#A7DBD7’;ctx。shadowOffsetX = 15;ctx。shadowOffsetY = 15;ctx。shadowBlur = 4;ctx。shadowColor = ‘rgba(204, 204, 204, 0。5)’;ctx。fillRect(90, 0, 45, 150);ctx。save();
當前畫布以及堆疊的完整狀態如下:
第四步:呼叫*ctx。restore()*獲取堆疊的最新狀態,然後使用該狀態繪製一個圓形。
ctx。restore();ctx。beginPath();ctx。arc(185, 75, 22, 0, Math。PI * 2, true);ctx。closePath();ctx。fill();
當前畫布以及堆疊的完整狀態如下:
第五步:繼續呼叫*ctx。restore()*獲取堆疊的最新狀態,然後使用該狀態繪製一個圓形。
ctx。restore();ctx。beginPath();ctx。arc(260, 75, 15, 0, Math。PI * 2, true);ctx。closePath();ctx。fill();
當前畫布以及堆疊的完整狀態如下:
第六步:繼續呼叫*ctx。restore()*獲取堆疊的最新狀態,然後使用該狀態繪製一個圓形。
ctx。restore();ctx。beginPath();ctx。arc(305, 75, 8, 0, Math。PI * 2, true);ctx。closePath();ctx。fill();
當前畫布以及堆疊的完整狀態如下:
4。本章小結
本章節以圖解的方式講解了 Canvas 中心點在繪圖中的作用,主要透過一個常見的元素 180° 旋轉的真例項子展開。同時,重點介紹了兩個 Canvas 繪圖狀態設定的 save()和 restore()方法。透過本章節的學習,應該會對 Canvas 繪製的上下文相關內容有一個比較深入的理解了。
參考資料
Understanding save() and restore() for the Canvas Context