一、基本概念
什麼是Canvas
儘管在 Mozilla 已經有不少關於 Canvas 的教程,我還是決定把自己的學習過程記錄下來。如果你覺得我寫的不夠明白,那麼你可以在參考資料中找到 Mozilla 網站上 Canvas 教學的連結。
另外,可以在這裡 找到一些有趣的 Canvas 範例
開始使用 Canvas
使用 Canvas 很簡單,與使用其他 HTML 元素一樣,只需要在頁面中新增一個
當然,這樣只是簡單的創建了一個 Canvas 對象而已,並沒有對它進行任何操作,這個時候的 canvas 元素看上去與 div 元素是沒什麼區別的,在頁面上什麼都看不出來:)
另外,canvas 元素的大小可以透過 width 與 height 屬性來指定,這與 img 元素有點相似。
Canvas 的核心:Context
前面說到可以透過 JavaScript 來操作 Canvas 物件來進行繪製圖形、合成影像等操作,這些操作並不是透過 Canvas 是透過 Canvas 物件的一個方法 getContext 來取得 Canvas 操作上下文來進行。也就是說,在後面我們使用 Canvas 物件的過程中,都是與 Canvas 物件的 Context 打交道,而 Canvas 物件本身可以用來取得 Canvas 物件的大小等資訊。
要取得 Canvas 物件的 Context 很簡單,直接呼叫 canvas 元素的 getContext 方法即可,已呼叫的時候需要傳遞一個 Context 型參數,目前可用的且是唯一可用的類型值就是 2d:
Firefox 3.0.x 的尷尬
Firefox 3.0.x 雖然支援了 canvas 元素,但並沒有完全按照規範來實現,規範中的 fillText、measureText 兩個方法在 Firefox 3.0.x 中被幾個 Firefox .x 中使用 Canvas 時需要先 fix 這個幾個方法在不同瀏覽器中的差異。
下面這程式碼取自 Mozilla Bespin 項目,它修正了 Firefox 3.0.x 中 Canvas 的 Context 對象與 HTML5 規範不一致的地方:
function fixContext(ctx) {
// * upgrade Firefox 3.0.x text rendering to HTML 5 standard
if (!ctx.fillText && ctx.mozDrawText) {
if (!ctx.fillText && ctx.mozDrawText) {
if (!ctx.fillText && ctx.mozDrawText) {
p. textToDraw, x, y, maxWidth) {
ctx.translate(x, y);
ctx.mozTextStyle = ctx.font;
ctx.mozDrawText(textToDraw);
ctx.translate()- x, -y);
};
}
// * Setup measureText
if (!ctx.measureText && ctx.mozMeasureText) {
ctx.measureText = function(text) {
if (ctx.font) ctx.mozTextStyle = ctx.font;
var width = ctx.mozMeasureText(text);
return { width: width };
};
}
}
}; 🎜>// * Setup html5MeasureText
if (ctx.measureText && !ctx.html5MeasureText) {
ctx.html5MeasureText = ctx.measureText;
ctx.html5MeasureText = ctx.measureText;
ctx.measureText = function( = ctx.html5MeasureText(text);
// fake it 'til you make it
textMetrics.ascent = ctx.html5MeasureText("m").width;
return textMetrics;
; 🎜>}
// * for other browsers, no-op away
if (!ctx.fillText) {
ctx.fillText = function() {};
}
if ( !ctx.measureText) {
ctx.measureText = function() { return 10; };
}
}
注意:到 Opera 9.5 為止,Opera 還不支援 HTML5 規範中 Canvas 對象的 fillText 及其相關方法與屬性。Hello, Canvas!
在對 Canvas 進行了一些初步了解後,開始來寫我們的第一個 Canvas 程序,聞名的 HelloWorld 的另一個分支「Hello, Canvas」:
(function() {
var canvas = document.getElementById("screen");
var ctx = fixContext(canvas.getContext("2d"));
ctx.font = "20pt Arial"
ctx.fillText("你好,Canvas!文字渲染升級到HTML 5 標準
if (!ctx.fillText && ctx.mozDrawText) {
ctx.fillText = function(textToDraw, x, y, maxWidth) {
ctx.translate(x, y );
ctx.mozDrawText(textToDraw);
ctx.translate(-x, -y) >}; *measureText
if (!ctx.measureText && ctx.mozMeasureText) {
ctx. measureText = function(text) {
if (ctx.font ) ctx.mozTextStyle = ctx.font
; var width = ctx.mozMeasureText(text);
return { width: width }
/ * 設定html5MeasureText
if (ctx.measureText && !ctx.html5MeasureText) {
ctx.html5MeasureText = ctx.measureText
ctx.measureText = 函式( (text);
// 修改它,直到成功
textMetrics.ascent = ctx.html5MeasureText("m").width;
返回textMetrics;
};
}
// * 其他瀏覽器,無操作
if (!ctx.fillText) {
ctx.fillText = function() { };
}
if (!ctx.measureText) {
ctx.measureText = function() { return 10; } } };
}
回傳 ctx;
}
})();
腳本>
運行範例,Canvas 物件所在區域顯示出“Hello, World!”,這就是程式碼中 ctx.fillText("Hello, World!", 20, 20); 的作用。
fillText 以及相關屬性
fillText方法用於在Canvas中顯示文字,它可以接受四個參數,其中最後一個是可選的:
void fillText(在 DOMString 文本中,在 float x 中,在 float y 中,[任選]在 float maxWidth 中);
其中maxWidth表示顯示文字時最大的寬度,可以防止文字溢出,不過我在測試中發現在Firefox和Chomre中指定了maxWidth時也沒有任何效果。
在使用 fillText 方法之前,可以透過設定 Context 的字體屬性來調整顯示文字的字體,在上面的範例中我使用了「20pt Arial」來作為顯示文字的字體,你可以自己設定不同的值來看具體效果。 二、路徑
圖形的基礎 - 路徑
在Canvas中,所有基本圖形都是以路徑為基礎的,所以,我們在呼叫2dContext的lineTo、rect等方法時,其實就是往已經的context路徑集合中再添加一些路徑點,在最後使用填充或描邊方法進行較差時,均參考這些路徑點來進行填充或畫線。 在每次开始不平等的路径之前,都应该使用 context.beginPath() 方法来告诉 Context 对象开始不平等的一个新的路径,否则后续的不平等的路径会与之前不平等的路径的另一个,在填充或画出对应的路径当完成路径后,可以直接使用context.closePath()方法来关闭路径,或者手动关闭路径。另外,如果在填充时路径没有关闭,那么Context会自动调用closePath方法将路径关闭。
基本路徑方法1。
這兩個方法在前面已經介紹過,分別用來通知Context開始一個新的路徑和關閉目前的路徑。 在Canvas中使用路徑時,應該保持一個良好的習慣,每次開始不同路徑前都要呼叫一次beginPath方法,否則畫出來的效果難看不說,還會嚴重影響效能。 在下面這張圖中,左邊的圖形則在當前估值前都調用了一次開始路徑來清除之前的路徑並重新開始估值新的路徑,而後面的圖形則只在估值前調用了一次beginPath來明顯的路徑,因此,雖然這裡使用的熟悉的顏色是#666,但是右邊的圖形顏色比左邊的深一些,每次使用因為中風相似的相似時,加上之前的路徑再次相似,這那時顏色就起來了就比原來深一些。
在 Context 中路徑數較少時,如果不考慮顯示效果,性能上還可以接受,但是如果 Context 中的路徑數很多時,在開始繪製新路徑前不使用 beginPath 的話,因為每次繪製都要將先前的路徑重新繪製一遍,這時效能會以指數下降。
因此,除非有特殊需要,每次開始繪製路徑前都要呼叫 beginPath 來開始新路徑。
2. 移動與直線 moveTo, lineTo, rect
void moveTo(in float x, in float y);
在 Canvas 中繪製路徑,一般是不需要指定起點的,預設的起點就是上一次繪製路徑的終點,因此,如果需要指定起點的話,就需要使用 moveTo 方法來指定要移動到的位置。
void lineTo(in float x, in float y);
lineTo 方法則是繪製一條直接路徑到指定的位置。在調用完 lineTo 方法後,Context 內部的繪圖起點會移到直線的終點。
void rect(in float x, in float y, in float w, in float h);
rect 方法用來繪製一個矩形路徑,透過參數指定左上角位置以及寬和高。在呼叫 rect 後,Context 的繪圖起點會移到 rect 所繪製的矩形的左上角。
rect 方法與後面要介紹的 arc 方法與其他路徑方法有一點不同,它們是使用參數指定起點的,而不是使用 Context 內部維護的起點。
3. 曲線 arcTo, arc, quadraticCurveTo, bezierCurveTo
void arcTo(in float x1, in float y1, in float x2, in float y2, in float 依照 WHATWG 文件的說明,這個方法是畫一個與兩條射線相切的的圓弧,兩條射線其中一條為穿過 Context 繪製起點,終點為 (x1, y1),另外一條為穿過(x2, y2),終點為 (x1, y1),這條圓弧為最小的與這兩條射線相切的圓弧。調用完 arcTo 方法後,將 圓弧與 射線 (x1, y1)-(x2, y2) 的切點加到目前路徑中,並為下次繪製的起點。
在測試中發現,Firefox 和 Opera 目前對此方法的支援並不好,只有 Chrome 和 Safari 4 能畫出正確的路徑。
void arc(in float x, in float y, in float radius, in float startAngle, in arc 方法用來繪製一段圓弧路徑,透過圓心位置、起始弧度、終止弧度來指定圓弧的位置和大小,這個方法也
不依賴 Context 維護的繪製起點。而在畫圓弧時的旋轉方向則由最後一個參數 anticlockwise 來指定,若為 true 就是逆時針,false 則為順時針。
void quadraticCurveTo(in float cpx, in float cpy, in float x, in float y);quadraticCurveTo 方法用來繪製二次樣條曲線路徑,參數中 cpx 與 cpy 指定控制點的位置,x 和 y 指定終點的位置,起點則是由 Context 維護的繪圖起點。
void bezierCurveTo(in float cp1x, in float cp1y, in float cp2x, in float cp2y,inbezierCurveTo 方法用來繪製貝塞爾曲線路徑,它與 quadraticCurveTo 相似,不過貝塞爾曲線有兩個控制點,因此參數中的 cp1x, cp1y, cp2x, cp2y 用來指定兩個控制點的位置,而 x 和 y 指定綹的位置。
var ctx = canvas.getContext("2d");
ctx.translate(10, 10);
ctx.beginPath();
ctx.arc(50, 50, 50, 0, Math.PI, true);
ctx.stroke();
// quadraticCurveTo
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.moveTo(110, 50);
ctx.quadraticCurveTo(160, 0, 210, 50);
ctx.stroke();
ctx.beginPath();
ctx. "red";
ctx.moveTo(110, 50);
ctx.lineTo(160, 0);
ctx.lineTo(210, 50);
ctx.stroke();
// bezierCurveTo
ctx.beginPath();
ctx.strokeStyle = "#000";
ctx.moveTo(220, 50);
ctx.bezierCurveTo(220, 50);
ctx.bezierCurveTo(2500, 2500, 25800,2500 , 10, 320, 50);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "red";
ctx.moveTo(220, 50); 🎜>ctx.lineTo(250, 0);
ctx.lineTo(280, 10);
ctx.lineTo(320, 50);
ctx.stroke();
4. fill, stroke, clip
fill 與 stroke 這兩個方法都很理解,分別用來填滿路徑與繪製路徑線。 clip 方法用來為 Canvas 設定一個剪輯區域,在呼叫 clip 方法之後的程式碼只對這個設定的剪輯區域有效,不會影響其他地方,這個方法在要進行局部更新時很有用。預設情況下,剪輯區域是一個左上角在 (0, 0),寬和高分別等於 Canvas 元素的寬和高的矩形。
在畫這張圖時,雖然兩次都是用 fillRect(0, 0, 100, 100) 填滿了一個 100x100 大小矩形,但是顯示的結果卻是第二次填滿的只是中間的一小塊,這是因為在兩次填滿之間使用 clip 方法設定了剪輯區域,因此在第二次填滿時只會影響到設定的中間那一小部分區域。
5. clearRect, fillRect, strokeRect
這三個方法不是路徑方法,而是用來直接處理 Canvas 上的內容,相當於 Canvas 的背景,調用這三個方法也不會影響 Context 繪圖的起點。
要清除 Canvas 上的所有內容時,可以直接呼叫 context.clearRect(0, 0, width, height) 來直接清除,而不需要使用路徑法繪製一個與 Canvas 相同大小的矩形路徑再使用 fill 方法去清除。
結語
透過 Canvas 的路徑法,可以使用 Canvas 處理一些簡單的向量圖形,因此在縮放時也不會失真。不過 Canvas 的路徑法也不是很強大,至少連個橢圓的路徑都沒有…
參考資料
1. The Canvas Element, WHATWG