HTML5 Canvas的圖形變換
很多時候,我們繪製出一個圖形之後,並不能達到我們預期的效果,這個時候,適當地運用圖形的變換(transformations,如旋轉和縮放等),可以創建出大量複雜多變的圖形。
1、儲存和恢復Canvas狀態
Canvas指的是當前畫面的所有樣式、變形和裁切的一個快照,以堆的方式儲存。save和restore方法用於儲存和恢復Canvas狀態,這兩個方法都不需要任何引數,用法如下:
context.save();
context.restore();
sava方法可以暫時將當前狀態儲存在堆中,這些狀態可以是各種屬性(如strokeStyle、fillStyle和globalCompositeOperation等)的值。當前應用的變形、當前裁切的路徑等。restore方法用於將上一個儲存狀態從堆中再次取出,恢復該狀態的所有設定。
舉一個簡單的示例:
<body> <canvas id="myCanvas" style="border: 1px solid #000" width="300" height="200"></canvas> <script> var c = document.getElementById("myCanvas"); var context = c.getContext("2d"); // 開始繪製矩形 context.fillStyle = "red"; context.strokeStyle = "blue"; context.fillRect(20,20,100,100); context.strokeRect(10,10,120,120); context.fill(); context.stroke(); // 儲存當前Canvas狀態 context.save(); // 繪製另一個矩形 context.fillStyle = "orange"; context.strokeStyle = "green"; context.fillRect(150,20,100,100); context.strokeRect(140,10,120,120); context.fill(); context.stroke(); // 恢復第一個矩形的狀態 context.restore(); // 繪製兩個矩形 context.fillRect(20,140,50,50); context.strokeRect(140,140,50,50); </script> </body>
先來看看最後的效果圖:
我們儲存的Canvas狀態屬性值是fillStyle填充為紅色,strokeStyle輪廓為藍色,當我們沒有呼叫restore方法恢復時,依舊使用的是我們自己定義的屬性值。但是,我們可以看到當我們呼叫restore後,繪製出來的矩形填充以及輪廓就是我們之前儲存的值。
2、移動座標空間
在上一篇Canvas元素中我們提到過,畫布的座標空間預設的是以畫布左上角(0,0)為原點,x軸水平向右為正向,y軸垂直向下為正向。而在繪製圖形時,我們可以使用translate方法移動座標空間,使畫布的變換矩陣發生水平和垂直方向的偏移,其用法如下:
context.translate(dx,dy);
其中dx和dy分別為座標原點沿水平和垂直兩個方向的偏移量。如下圖所示:
當然了,在進行圖形變換之前,最好先要養成使用save方法儲存當前狀態的好習慣。在許多情況下,使用restore方法來自動恢復原來的狀態要比手動恢復更高效,特別是當重複某種運算時。接下來,我們就通過繪製一個傘狀圖形的示例加深對它的理解。
<body>
<canvas id="myCanvas" style="border: 1px solid red;" width="700" height="200"></canvas>
</body>
<script>
function drawTop(ctx,fillStyle){
ctx.fillStyle = fillStyle;
ctx.beginPath();
// 以座標原點(0,0)為圓心,半徑為30px,0為開始的角度,Math.PI為結束的角度,即180度,最後一個引數表示順時針方向繪製
ctx.arc(0,0,30,0,Math.PI,true);
ctx.closePath();
ctx.fill();
}
function drawGrip(ctx){
// 儲存了之前定義的屬性值
ctx.save();
ctx.fillStyle = "blue";
// 以(-1.5,0)為起始座標,繪製寬為1.5px,高為40px的矩形
// 這裡解釋一下為什麼以(-1.5,0)為座標,是因為後面需要繪製的寬為1.5px,所以提前移動過去,繪製的時候右移就到了中心的位置
ctx.fillRect(-1.5,0,1.5,40);
ctx.beginPath();
ctx.strokeStyle = "blue";
// 這就是我們看到的傘的下部分傘鉤
ctx.arc(-5,40,4,Math.PI,Math.PI*2,true);
ctx.stroke();
ctx.closePath();
// 恢復了之前定義的屬性值
ctx.restore();
}
function draw(){
var ctx = document.getElementById("myCanvas").getContext("2d");
// 垂直方向和水平方向發生了偏移
ctx.translate(80,80);
// 迴圈10次,表明有10個傘狀圖形
for(var i=0;i<10;i++){
ctx.save();
// 水平方向發生偏移
ctx.translate(60*i,0);
// 繪製傘狀圖形的上半部分,也就是半圓部分,第二個引數表示填充顏色
drawTop(ctx,"rgb("+(30*i)+","+(255-30*i)+",255)");
// 繪製傘狀圖形的下半部分
drawGrip(ctx);
ctx.restore();
}
}
// 在頁面或影象載入完後觸發
window.onload = function(){
draw();
}
</script>
最後的效果圖:
在這裡,我只想說的一點是,關於
ctx.fillRect(-1.5,0,1.5,40);和ctx.arc(-5,40,4,Math.PI,Math.PI*2,true);
這兩個語句的座標問題,主要是取決於你後面繪製的圖形的寬度。
Canvas中圖形移動的實現,實際上是通過改變畫布的座標原點實現的,所謂的“移動圖形”,只是看上去被移動的樣子,移動的是其實是座標空間。
3、旋轉座標空間
rotate方法用於以原點為中心旋轉Canvas,實質上旋轉的是Canvas上下文物件的座標空間,具體用法是:
context.rotate(angle);
rotate方法只有一個引數,即旋轉角度angle,其順時針方向為正方向,以弧度為單位,旋轉中心是Canvas的原點。
拿上一個傘狀例子來說,我現在的狀態是一行排列的,那麼我現在想要將它旋轉成一個圓形佇列,又該怎麼實現呢?
function draw(){
var ctx = document.getElementById("myCanvas").getContext("2d");
// 垂直方向和水平方向發生了偏移
ctx.translate(150,150);
for(var i=1;i<9;i++){
ctx.save();
// 圖片旋轉角度
ctx.rotate(Math.PI*(2/4+i/4));
// 水平方向發生偏移
ctx.translate(0,-100);
// 繪製傘狀圖形的上半部分,也就是半圓部分,第二個引數表示填充顏色
drawTop(ctx,"rgb("+(30*i)+","+(255-30*i)+",255)");
// 繪製傘狀圖形的下半部分
drawGrip(ctx);
ctx.restore();
}
}
其實,到最後,我們會發現,只是draw方法發生了變化。具體是什麼變化呢?無非就是多了ctx.rotate(Math.PI*(2/4+i/4));和ctx.translate(0,-100);語句。那麼接下來,我們一起思考為什麼要使用這兩個方法裡的資料。
首先,我們首先明確Math.PI是180度,那麼很顯然旋轉一圈是360度,也就是說,後面的引數(2/4+i/4)中,i=1時表示初始旋轉的位置,後面的則依次遞加。
其次,為什麼每次都要將座標空間沿y軸負方向移動100px呢?那是因為,如果我們不移動,最後所有的圖形都會重合成一個圓,就像下面這樣:
而我們實際想要的效果是什麼呢?
4、縮放圖形
scale方法用於增減Canvas上下文物件中的畫素數目,從而實現圖形或點陣圖的放大或縮小,其用法是:
context.scale(x,y);
其中x,y為必須接受的引數,x為橫軸的縮放因子,y軸為縱軸的縮放因子,它們的值必須是正值。
若放大圖形,引數值需大於1;
若縮小圖形,引數值需小於1;
若引數值等於1,無任何效果。
我們可以用一個螺旋狀由大到小的例子來深入理解縮放的使用:
<body>
<canvas id="myCanvas" style="border: 1px solid red;" width="700" height="300"></canvas>
</body>
<script>
function draw(){
var ctx = document.getElementById("myCanvas").getContext("2d");
// 垂直方向和水平方向發生了偏移
ctx.translate(200,20);
for(var i=1;i<80;i++){
ctx.save();
ctx.translate(30,30);
ctx.scale(0.95,0.95);
ctx.rotate(Math.PI/12);
ctx.beginPath();
ctx.fillStyle = "red";
ctx.globalAlpha = "0.4";
ctx.arc(0,0,50,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
}
}
// 在頁面或影象載入完後觸發
window.onload = function(){
draw();
}
</script>
其實,這也不難理解,我們反反覆覆使用的都是提到過的方法,唯一的不同是,我們現在把所有的方法整合在了一起,我們傳遞的引數不再是一個確定的值,所以,我們實現的效果也是千變萬化的。
關於Canvas元素其實還有其他的很多方法,後續將會繼續補充,希望大家能夠共同進步!