本來今天想開始學習怎麼在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 |
當lineJoin 爲miter 時,這個屬性指定斜連接長度和二分之一線寬的最大比率 |
shadowBlur |
指定陰影模糊度 |
shadowColor |
指定陰影顏色 |
shadowOffsetX |
指定陰影水平偏移值 |
shadowOffsetY |
指定陰影垂直偏移值 |
strokeStyle |
指定線段顏色 |
上面是Canvas繪圖中的狀態,那麼什麼情形不屬於Canvas狀態。
在Canvas中當前路徑和當前位圖受Canvas環境控制,不屬於保存狀態。這個重要的功能允許在畫布上對單個對象進行繪畫和製作動畫。
雖然說了那麼多,但僅僅知道是用來操作Canvas狀態,對於初學者而言還是模棱兩棵。在官方文檔中是這樣描述的:
save()
和restore()
方法允許你保存和恢復一個CanvasRenderingContext2D
對象的狀態。save()
把當前狀態推入到繪圖堆棧中,而restore()
從繪圖堆棧中的頂端彈出最近保存的狀態,並且根據這些存儲的值來設置當前繪圖狀態。
簡單來說,save()
主要用來保存目前Canvas的狀態,例如lineWidth
、fillStyle
、lineJoin
等,通過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。