Canvas學習:save()和restore()

本來今天想開始學習怎麼在Canvas中繪製矩形。但昨天發朋友圈,聊到Canvas。有網友提醒我將所有繪製的東西放在ctx.save()ctx.restore(),能起到保存繪製狀態和防止污染狀態棧。養成一個良好的習慣。

此時一臉蒙逼!因爲我並不知道這兩個東東是什麼意思,怎麼使用?

後來翻了書,也只是提到了在Canvas中有save()restore()兩個方法,它們都是屬於Canvas中CanvasRenderingContext2D中與狀態操作有關的兩個方法。並沒有詳細的介紹,對於初學者的我而言,也是一知半解。下面的內容記錄了我對save()restore()兩個方法的一些理解。

Canvas 狀態的保存和恢復

Canvas的API提供了兩個名叫save()restore()的方法,用於保存及恢復當前Canvas繪圖環境的所有屬性。其中save()可以保存當前狀態,而restore()可以還原之前保存的狀態。

這兩個方法再繪圖中有着重要的作用,比如我們在繪圖的時候需要使用多種顏色,顏色需要不時的切換。那麼使用save()restore()方法即可比較方便的實現此功能。

理解save()和restore()

對於save()restore()方法,一開始有一個錯誤的理解,以爲每一步都save()之後restore()就等同於command + z(或者ctrl + z),其實save()保存的只是CanvasRenderingContext2D對象的狀態以及對象的所有屬性,並不包括這個對象上繪製的圖形。

Canvas中狀態和非狀態

在Canvas環境中繪圖時,可以利用所謂的繪圖堆棧狀態。每個狀態隨時存儲Canvas上下文數據。下面是存儲在狀態堆棧的數據列表。

  • 當前的座標變換(變換矩陣)信息,比如旋轉或平移時使用的rotate()setTransform()方法
  • 當前剪貼區域
  • 圖形上下文對象(CanvasRenderingContext2D)的當前屬性值

CanvasRenderingContext2D的當前屬性值主要包括:

屬性 描述
canvas 取得畫布<canvas>元素
fillStyle 填充路徑的當前的顏色、模式或漸變
globalCompositeOperation 指定顏色如何與畫布上已有顏色組合(合成)
lineCap 指定線段端點的繪製方式
lineJoin 指定線段連接的繪製方式
lineWidth 繪製線段的寬度
miterLimit lineJoinmiter時,這個屬性指定斜連接長度和二分之一線寬的最大比率
shadowBlur 指定陰影模糊度
shadowColor 指定陰影顏色
shadowOffsetX 指定陰影水平偏移值
shadowOffsetY 指定陰影垂直偏移值
strokeStyle 指定線段顏色

上面是Canvas繪圖中的狀態,那麼什麼情形不屬於Canvas狀態。

在Canvas中當前路徑和當前位圖受Canvas環境控制,不屬於保存狀態。這個重要的功能允許在畫布上對單個對象進行繪畫和製作動畫。

雖然說了那麼多,但僅僅知道是用來操作Canvas狀態,對於初學者而言還是模棱兩棵。在官方文檔中是這樣描述的:

save()restore()方法允許你保存和恢復一個CanvasRenderingContext2D對象的狀態。save()把當前狀態推入到繪圖堆棧中,而restore()從繪圖堆棧中的頂端彈出最近保存的狀態,並且根據這些存儲的值來設置當前繪圖狀態。

簡單來說,save()主要用來保存目前Canvas的狀態,例如lineWidthfillStylelineJoin等,通過save()函數它會將目前Canvas的狀態推到繪圖堆棧中;而restore()函數就是從繪圖堆棧中彈出上一個Canvas的狀態。

畫張圖來幫助理解:

示例理解save()和restore()

如果上面的描述還是無法幫助你理解save()restore()兩個方法的話,那我們來寫一個簡單的示例來幫助大家理解。

先上示例代碼:

function drawScreen () {

    ctx.fillStyle = '#FA6900';
    ctx.shadowOffsetX = 5;
    ctx.shadowOffsetY = 5;
    ctx.shadowBlur    = 4;
    ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
    ctx.fillRect(10,10,15,150);
    ctx.save(); // 將第一個狀態推到堆棧中

    ctx.fillStyle = '#f36';
    ctx.shadowOffsetX = 10;
    ctx.shadowOffsetY = 10;
    ctx.shadowBlur    = 4;
    ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
    ctx.fillRect(50,10,30,150);
    ctx.save(); // 將第二個狀態推到堆棧中

    ctx.fillStyle = '#A7DBD7';
    ctx.shadowOffsetX = 15;
    ctx.shadowOffsetY = 15;
    ctx.shadowBlur    = 4;
    ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
    ctx.fillRect(110,10,45,150);
    ctx.save(); // 將第三個狀態推到堆棧中

    ctx.restore(); // 取出堆棧3(第三個狀態)
    ctx.beginPath();
    ctx.arc(225, 75, 22, 0, Math.PI*2, true);
    ctx.closePath();
    ctx.fill();

    ctx.restore(); // 取出堆棧2(第二個狀態)
    ctx.beginPath();
    ctx.arc(320, 75, 15, 0, Math.PI*2, true);
    ctx.closePath();
    ctx.fill();

    ctx.restore(); // 取出堆棧1(第一個狀態)
    ctx.beginPath();
    ctx.arc(400, 75, 8, 0, Math.PI*2, true);
    ctx.closePath();
    ctx.fill();

}

你將看到的效果如下:

通過這個示例來闡述save()restore()

首先使用fillRect()繪製了一個矩形,並且給這個矩形設置了填充顏色和陰影效果。然後通過save()將這個狀態添加到Canvas繪圖的堆棧中:

ctx.fillStyle = '#FA6900';
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(10,10,15,150);
ctx.save(); // 將第一個狀態推到堆棧中

然又繪製了一個矩形,同樣設置了填充和陰影效果,照樣通過save()將這個狀態添加到Canvas繪圖的堆棧中:

ctx.fillStyle = '#f36';
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(50,10,30,150);
ctx.save(); // 將第二個狀態推到堆棧中

使用類似的方法將第三個狀態添加到Canvas的堆棧中:

ctx.fillStyle = '#A7DBD7';
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 15;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(110,10,45,150);
ctx.save(); // 將第三個狀態推到堆棧中

現在通過restore()取出繪圖堆棧中保存的狀態,也就是設置矩形的填充色和陰影效果的屬性。然後將這個保存的狀態用到一個圓上面。將畫出的圓,除了形狀不一樣之外,其他的圖形樣式都將一樣:

ctx.restore(); // 取出堆棧3(第三個狀態)
ctx.beginPath();
ctx.arc(225, 75, 22, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

這個圓取出了繪圖堆棧中最頂上(最後)一個狀態。再次使用restore()將第二個狀態用於到另一個圓上:

ctx.restore(); // 取出堆棧2(第二個狀態)
ctx.beginPath();
ctx.arc(320, 75, 15, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

使用類似的方法將第一個狀態用到另一個圓上:

ctx.restore(); // 取出堆棧1(第一個狀態)
ctx.beginPath();
ctx.arc(400, 75, 8, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

看到這裏,是不是對Canvas中的save()restore()有了一個較清晰的認知了。

實例:製作一個扇形

在實際使用當中,save()restore()還是非常廣泛的,特別是涉及到座標系統的變換和圖形變換方面。最後通過一個簡單的示例,讓大家體驗一下:

// ctx: Canvas繪圖環境
// x,y: 位移目標點
// r: 圓弧半徑
// sDeg: 旋轉起始角度
// eDeg: 旋轉終點角度

function drawSector(ctx, x, y,r, sDeg, eDeg) {
    // 初始保存
    ctx.save();

    //位移到目標點
    ctx.translate(x, y);
    ctx.beginPath();

    // 畫出圓弧
    ctx.arc(0, 0, r, sDeg, eDeg);

    // 再次保存以備旋轉
    ctx.save();

    // 旋轉至起始角度
    ctx.rotate(eDeg);

    // 移動到終點,準備連接終點與圓心
    ctx.moveTo(r, 0);

    // 連接到圓心
    ctx.lineTo(0, 0);

    // 還原
    ctx.restore();

    // 旋轉至起點角度
    ctx.rotate(sDeg);

    // 從圓心連接到起點
    ctx.lineTo(r, 0);

    ctx.closePath();
    // 還原到最初保存的狀態
    ctx.restore();
}

然後調用這個封裝的函數:

function drawScreen () {

    var deg = Math.PI / 180;

    var obj = {
        x: 300,
        y: 150,
        r: 80,
        sDeg: [30, 111, 190, 233, 280, 345],
        eDeg: [111, 190, 233, 280, 345, 30],
        style: ['#f00', '#0f0', '#00f', '#789', '#abcdef']
    }

    for (var i = 0; i < obj.sDeg.length; i++) {
        drawSector(ctx,obj.x, obj.y, obj.r, obj.sDeg[i] * deg, obj.eDeg[i] * deg);
        ctx.fill();
        ctx.fillStyle = obj.style[i];
    }

}

最後你能看到的扇形效果如下:

總結

這篇文章主要介紹了Canvas中的save()restore()方法。其中save()方法會將Canvas的狀態推到Canvas繪圖的堆棧中,然後通過restore()方法從Canvas繪圖的堆棧中取出save()保存的狀態。

著作權歸作者所有。
商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
原文: http://www.w3cplus.com/canvas/canvas-states.html © w3cplus.com

個人建了前端學習羣,旨在一起學習前端。純淨、純粹技術討論,非前端人員勿擾!入羣加我微信iamaixiaoxiao。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章