1. 程式人生 > >HTML5 Canvas的圖形變換

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元素其實還有其他的很多方法,後續將會繼續補充,希望大家能夠共同進步!