HTML5 canvas 元素詳細教程四:運用樣式與顏色。
在 繪制圖形 的章節里,我只用到默認的線條和填充樣式。而在這一章里,我們將會探討 canvas 全部的可選項,來繪制出更加吸引人的內容。
色彩 Colors
到目前為止,我們只看到過繪制內容的方法。如果我們想要給圖形上色,有兩個重要的屬性可以做到:fillStyle
和strokeStyle。
fillStyle = color
strokeStyle = color
strokeStyle
是用于設置圖形輪廓的顏色,而 fillStyle
用于設置填充顏色。color
可以是表示 CSS 顏色值的字符串,漸變對象或者圖案對象。我們遲些再回頭探討漸變和圖案對象。默認情況下,線條和填充顏色都是黑色(CSS 顏色值 #000000)。
您輸入的應該是符合 CSS3 顏色值標準 的有效字符串。下面的例子都表示同一種顏色。
// 這些 fillStyle 的值均為 '橙色' ctx.fillStyle = "orange"; ctx.fillStyle = "#FFA500"; ctx.fillStyle = "rgb(255,165,0)"; ctx.fillStyle = "rgba(255,165,0,1)";
注意: 目前 Gecko 引擎并沒有提供對所有的 CSS 3 顏色值的支持。例如,hsl(100%,25%,0)
或者 rgb(0,100%,0)
都不可用。但如果您遵循上面例子的規范,應該不會有問題。
注意: 一旦您設置了 strokeStyle
或者 fillStyle
的值,那么這個新值就會成為新繪制的圖形的默認值。如果你要給每個圖形上不同的顏色,你需要重新設置 fillStyle
或 strokeStyle
的值。
fillStyle
示例
在本示例里,我會再度用兩層 for
循環來繪制方格陣列,每個方格不同的顏色。結果如右圖,但實現所用的代碼卻沒那么絢麗。我用了兩個變量 i 和 j 來為每一個方格產生唯一的 RGB 色彩值,其中僅修改紅色和綠色通道的值,而保持藍色通道的值不變。你可以通過修改這些顏色通道的值來產生各種各樣的色板。通過增加漸變的頻率,你還可以繪制出類似 Photoshop 里面的那樣的調色板。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); for (var i=0;i<6;i++){ for (var j=0;j<6;j++){ ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' + Math.floor(255-42.5*j) + ',0)'; ctx.fillRect(j*25,i*25,25,25); } } }
strokeStyle
示例
這個示例與上面的有點類似,但這次用到的是
strokeStyle
屬性,而且畫的不是方格,而是用arc
方法來畫圓。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); for (var i=0;i<6;i++){ for (var j=0;j<6;j++){ ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' + Math.floor(255-42.5*j) + ')'; ctx.beginPath(); ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true); ctx.stroke(); } } }
透明度 Transparency
除了可以繪制實色圖形,我們還可以用 canvas 來繪制半透明的圖形。通過設置 globalAlpha
屬性或者使用一個半透明顏色作為輪廓或填充的樣式。
globalAlpha = transparency value
這個屬性影響到 canvas 里所有圖形的透明度,有效的值范圍是 0.0 (完全透明)到 1.0(完全不透明),默認是 1.0。
globalAlpha
屬性在需要繪制大量擁有相同透明度的圖形時候相當高效。不過,我認為下面的方法可操作性更強一點。
因為 strokeStyle
和 fillStyle
屬性接受符合 CSS 3 規范的顏色值,那我們可以用下面的寫法來設置具有透明度的顏色。
// Assigning transparent colors to stroke and fill style ctx.strokeStyle = "rgba(255,0,0,0.5)"; ctx.fillStyle = "rgba(255,0,0,0.5)";
rgba()
方法與 rgb()
方法類似,就多了一個用于設置色彩透明度的參數。它的有效范圍是從 0.0(完全透明)到 1.0(完全不透明)。
globalAlpha
示例
在這個例子里,我用四色格作為背景,設置 globalAlpha
為 0.2后,在上面畫一系列半徑遞增的半透明圓。最終結果是一個徑向漸變效果。圓疊加得越更多,原先所畫的圓的透明度會越低。通過增加循環次數,畫更多的圓,背景圖的中心部分會完全消失。
- 這個例子在 Firefox 1.5 beta 1 里是行不通的。你需要 nightly branch build 或者等待新版本發布來實踐這個效果。
- 這個例子在 Safari 下可能由于顏色值無效而達不到效果。例子里是 '#09F' 是不符合規范要求的,不過 Firefox 是認識這種格式的。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); // draw background ctx.fillStyle = '#FD0'; ctx.fillRect(0,0,75,75); ctx.fillStyle = '#6C0'; ctx.fillRect(75,0,75,75); ctx.fillStyle = '#09F'; ctx.fillRect(0,75,75,75); ctx.fillStyle = '#F30'; ctx.fillRect(75,75,150,150); ctx.fillStyle = '#FFF'; // set transparency value ctx.globalAlpha = 0.2; // Draw semi transparent circles for (var i=0;i<7;i++){ ctx.beginPath(); ctx.arc(75,75,10+10*i,0,Math.PI*2,true); ctx.fill(); } }
rgba()示例
第二個例子和上面那個類似,不過不是畫圓,而是畫矩形。這里還可以看出,rgba() 可以分別設置輪廓和填充樣式,因而具有更好的可操作性和使用彈性。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); // Draw background ctx.fillStyle = 'rgb(255,221,0)'; ctx.fillRect(0,0,150,37.5); ctx.fillStyle = 'rgb(102,204,0)'; ctx.fillRect(0,37.5,150,37.5); ctx.fillStyle = 'rgb(0,153,255)'; ctx.fillRect(0,75,150,37.5); ctx.fillStyle = 'rgb(255,51,0)'; ctx.fillRect(0,112.5,150,37.5); // Draw semi transparent rectangles for (var i=0;i<10;i++){ ctx.fillStyle = 'rgba(255,255,255,'+(i+1)/10+')'; for (var j=0;j<4;j++){ ctx.fillRect(5+i*14,5+j*37.5,14,27.5) } } }
線型 Line styles
可以通過一系列屬性來設置線的樣式。
lineWidth = value
lineCap = type
lineJoin = type
miterLimit = value
我會詳細介紹這些屬性,不過通過以下的樣例可能會更加容易理解。
lineWidth 屬性的例子
這個屬性設置當前繪線的粗細。屬性值必須為正數。默認值是1.0。
線寬是指給定路徑的中心到兩邊的粗細。換句話說就是在路徑的兩邊各繪制線寬的一半。因為畫布的坐標并不和像素直接對應,當需要獲得精確的水平或垂直線的時候要特別注意。
在下面的例子中,用遞增的寬度繪制了10條直線。最左邊的線寬1.0單位。并且,最左邊的以及所有寬度為奇數的線并不能精確呈現,這就是因為路徑的定位問題。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); for (var i = 0; i < 10; i++){ ctx.lineWidth = 1+i; ctx.beginPath(); ctx.moveTo(5+i*14,5); ctx.lineTo(5+i*14,140); ctx.stroke(); } }
想要獲得精確的線條,必須對線條是如何描繪出來的有所理解。見下圖,用網格來代表 canvas 的坐標格,每一格對應屏幕上一個像素點。在第一個圖中,填充了 (2,1) 至 (5,5) 的矩形,整個區域的邊界剛好落在像素邊緣上,這樣就可以得到的矩形有著清晰的邊緣。
如果你想要繪制一條從 (3,1) 到 (3,5),寬度是 1.0 的線條,你會得到像第二幅圖一樣的結果。實際填充區域(深藍色部分)僅僅延伸至路徑兩旁各一半像素。而這半個像素又會以近似的方式進行渲染,這意味著那些像素只是部分著色,結果就是以實際筆觸顏色一半色調的顏色來填充整個區域(淺藍和深藍的部分)。這就是上例中為何寬度為 1.0 的線并不準確的原因。
要解決這個問題,你必須對路徑施以更加精確的控制。已知粗 1.0 的線條會在路徑兩邊各延伸半像素,那么像第三幅圖那樣繪制從 (3.5,1) 到 (3.5,5) 的線條,其邊緣正好落在像素邊界,填充出來就是準確的寬為 1.0 的線條。
對于那些寬度為偶數的線條,每一邊的像素數都是整數,那么你想要其路徑是落在像素點之間 (如那從 (3,1) 到 (3,5)) 而不是在像素點的中間。同樣,注意到那個例子的垂直線條,其 Y 坐標剛好落在網格線上,如果不是的話,端點上同樣會出現半渲染的像素點。
雖然開始處理可縮放的 2D 圖形時會有點小痛苦,但是及早注意到像素網格與路徑位置之間的關系,可以確保圖形在經過縮放或者其它任何變形后都可以保持看上去蠻好:線寬為 1.0 的垂線在放大 2 倍后,會變成清晰的線寬為 2.0,并且出現在它應該出現的位置上。
lineCap
屬性的例子
屬性
lineCap
的指決定了線段端點顯示的樣子。它可以為下面的三種的其中之一:butt
,round
和 square。默認是
butt。
這個例子里面,我繪制了三條直線,分別賦予不同的 lineCap
值。還有兩條輔助線,為了可以看得更清楚它們之間的區別,三條線的起點終點都落在輔助線上。
最左邊的線用了默認的 butt
。可以注意到它是與輔助線齊平的。中間的是 round
的效果,端點處加上了半徑為一半線寬的半圓。右邊的是 square
的效果,端點出加上了等寬且高度為一半線寬的方塊。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); var lineCap = ['butt','round','square']; // Draw guides ctx.strokeStyle = '#09f'; ctx.beginPath(); ctx.moveTo(10,10); ctx.lineTo(140,10); ctx.moveTo(10,140); ctx.lineTo(140,140); ctx.stroke(); // Draw lines ctx.strokeStyle = 'black'; for (var i=0;i<lineCap.length;i++){ ctx.lineWidth = 15; ctx.lineCap = lineCap[i]; ctx.beginPath(); ctx.moveTo(25+i*50,10); ctx.lineTo(25+i*50,140); ctx.stroke(); } }
lineJoin
屬性的例子
lineJoin
的屬性值決定了圖形中兩線段連接處所顯示的樣子。它可以是這三種之一:round
,bevel
和 miter。默認是
miter
。
這里我同樣用三條折線來做例子,分別設置不同的 lineJoin
值。最上面一條是 round
的效果,邊角處被磨圓了,圓的半徑等于線寬。中間和最下面一條分別是 bevel 和 miter 的效果。當值是 miter
的時候,線段會在連接處外側延伸直至交于一點,延伸效果受到下面將要介紹的miterLimit
屬性的制約。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); var lineJoin = ['round','bevel','miter']; ctx.lineWidth = 10; for (var i=0;i<lineJoin.length;i++){ ctx.lineJoin = lineJoin[i]; ctx.beginPath(); ctx.moveTo(-5,5+i*40); ctx.lineTo(35,45+i*40); ctx.lineTo(75,5+i*40); ctx.lineTo(115,45+i*40); ctx.lineTo(155,5+i*40); ctx.stroke(); } }
miterLimit
屬性的演示例子
就如上一個例子所見的應用
miter
的效果,線段的外側邊緣會延伸交匯于一點上。線段直接夾角比較大的,交點不會太遠,但當夾角減少時,交點距離會呈指數級增大。
miterLimit
屬性就是用來設定外延交點與連接點的最大距離,如果交點距離大于此值,連接效果會變成了 bevel。
我做了一個演示頁面,你可以動手改變 miterLimit 的值,觀察其影響效果。藍色輔助線顯示鋸齒折線段的起點與終點所在的位置。
漸變 Gradients
就好像一般的繪圖軟件一樣,我們可以用線性或者徑向的漸變來填充或描邊。我們用下面的方法新建一個 canvasGradient
對象,并且賦給圖形的 fillStyle
或 strokeStyle
屬性。
createLinearGradient(x1,y1,x2,y2)
createRadialGradient(x1,y1,r1,x2,y2,r2)
createLinearGradient
方法接受 4 個參數,表示漸變的起點 (x1,y1) 與終點 (x2,y2)。createRadialGradient
方法接受 6 個參數,前三個定義一個以 (x1,y1) 為原點,半徑為 r1 的圓,后三個參數則定義另一個以 (x2,y2) 為原點,半徑為 r2 的圓。
var lineargradient = ctx.createLinearGradient(0,0,150,150); var radialgradient = ctx.createRadialGradient(75,75,0,75,75,100);
創建出 canvasGradient
對象后,我們就可以用 addColorStop
方法給它上色了。
addColorStop(position, color)
addColorStop
方法接受 2 個參數,position
參數必須是一個 0.0 與 1.0 之間的數值,表示漸變中顏色所在的相對位置。例如,0.5 表示顏色會出現在正中間。color
參數必須是一個有效的 CSS 顏色值(如 #FFF, rgba(0,0,0,1),等等)。
你可以根據需要添加任意多個色標(color stops)。下面是最簡單的線性黑白漸變的例子。
var lineargradient = ctx.createLinearGradient(0,0,150,150); lineargradient.addColorStop(0,'white'); lineargradient.addColorStop(1,'black');
createLinearGradient
的例子
本例中,我弄了兩種不同的漸變。第一種是背景色漸變,你會發現,我給同一位置設置了兩種顏色,你也可以用這來實現突變的效果,就像這里從白色到綠色的突變。一般情況下,色標的定義是無所謂順序的,但是色標位置重復時,順序就變得非常重要了。所以,保持色標定義順序和它理想的順序一致,結果應該沒什么大問題。
第二種漸變,我并不是從 0.0 位置開始定義色標,因為那并不是那么嚴格的。在 0.5 處設一黑色色標,漸變會默認認為從起點到色標之間都是黑色。
你會發現,strokeStyle
和 fillStyle
屬性都可以接受 canvasGradient
對象。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); // Create gradients var lingrad = ctx.createLinearGradient(0,0,0,150); lingrad.addColorStop(0, '#00ABEB'); lingrad.addColorStop(0.5, '#fff'); //lingrad.addColorStop(0.5, '#26C000'); //lingrad.addColorStop(1, '#fff'); var lingrad2 = ctx.createLinearGradient(0,50,0,95); lingrad2.addColorStop(0.5, '#000'); lingrad2.addColorStop(1, 'rgba(0,0,0,0)'); // assign gradients to fill and stroke styles ctx.fillStyle = lingrad; ctx.strokeStyle = lingrad2; // draw shapes ctx.fillRect(10,10,130,130); ctx.strokeRect(50,50,50,50); }
createRadialGradient
的例子
這個例子,我定義了 4 個不同的徑向漸變。由于可以控制漸變的起始與結束點,所以我們可以實現一些比(如在 Photoshop 中所見的)經典的徑向漸變更為復雜的效果。(經典的徑向漸變是只有一個中心點,簡單地由中心點向外圍的圓形擴張)。
這里,我讓起點稍微偏離終點,這樣可以達到一種球狀 3D 效果。但最好不要讓里圓與外圓部分交疊,那樣會產生什么效果就真是不得而知了。
4 個徑向漸變效果的最后一個色標都是透明色。如果想要兩色標直接的過渡柔和一些,只要兩個顏色值一致就可以了。代碼里面看不出來,是因為我用了兩種不同的顏色表示方法,但其實是相同的,#019F62 = rgba(1,159,98,1)。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); // Create gradients var radgrad = ctx.createRadialGradient(45,45,10,52,50,30); radgrad.addColorStop(0, '#A7D30C'); radgrad.addColorStop(0.9, '#019F62'); radgrad.addColorStop(1, 'rgba(1,159,98,0)'); var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50); radgrad2.addColorStop(0, '#FF5F98'); radgrad2.addColorStop(0.75, '#FF0188'); radgrad2.addColorStop(1, 'rgba(255,1,136,0)'); var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40); radgrad3.addColorStop(0, '#00C9FF'); radgrad3.addColorStop(0.8, '#00B5E2'); radgrad3.addColorStop(1, 'rgba(0,201,255,0)'); var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90); radgrad4.addColorStop(0, '#F4F201'); radgrad4.addColorStop(0.8, '#E4C700'); radgrad4.addColorStop(1, 'rgba(228,199,0,0)'); // draw shapes ctx.fillStyle = radgrad4; ctx.fillRect(0,0,150,150); ctx.fillStyle = radgrad3; ctx.fillRect(0,0,150,150); ctx.fillStyle = radgrad2; ctx.fillRect(0,0,150,150); ctx.fillStyle = radgrad; ctx.fillRect(0,0,150,150); }
圖案 Patterns
上一節的一個例子里面,我用了循環來實現圖案的效果。其實,有一個更加簡單的方法:createPattern。
createPattern(image,type)
該方法接受兩個參數。Image 可以是一個 Image
對象的引用,或者另一個 canvas 對象。Type 必須是下面的字符串值之一:repeat
,repeat-x
,repeat-y
和 no-repeat
。
Image
參數在 Firefox 1.5 (Gecko 1.8) 中是無效的。圖案的應用跟漸變很類似的,創建出一個 pattern 之后,賦給 fillStyle
或 strokeStyle
屬性即可。
var img = new Image(); img.src = 'someimage.png'; var ptrn = ctx.createPattern(img,'repeat');
注意:與 drawImage 有點不同,你需要確認 image 對象已經裝載完畢,否則圖案可能效果不對的。
注意:Firefox 目前只支持屬性值repeat
。如果賦其它值,什么效果都沒有的。
createPattern
的例子
這最后的例子,我創建一個圖案然后賦給了
fillStyle
屬性。值得一提的是,使用 Image 對象的 onload
handler 來確保設置圖案之前圖像已經裝載完畢。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); // create new image object to use as pattern var img = new Image(); img.src = 'images/wallpaper.png'; img.onload = function(){ // create pattern var ptrn = ctx.createPattern(img,'repeat'); ctx.fillStyle = ptrn; ctx.fillRect(0,0,150,150); } }
陰影 Shadows
Firefox 3.5 note
Firefox 3.5 中支持陰影效果.
Firefox 3.5 (Gecko 1.9.1) 在 canvas 中加入了對陰影的支持,就 4 個屬性。
shadowOffsetX = float
shadowOffsetY =
float
shadowBlur =
float
shadowColor =
color
shadowOffsetX
和 shadowOffsetY
用來設定陰影在 X 和 Y 軸的延伸距離,它們是不受變換矩陣所影響的。負值表示陰影會往上或左延伸,正值則表示會往下或右延伸,他們默認都是 0。
shadowBlur
用于設定陰影的模糊程度,其數值并不跟像素數量掛鉤,也不受變換矩陣的影響,默認為 0。
shadowColor
用于設定陰影效果的延伸,值可以是標準的 CSS 顏色值,默認是全透明的黑色。
文字陰影的例子
這個例子繪制了帶陰影效果的文字。
function draw() { var ctx = document.getElementById('canvas').getContext('2d'); ctx.shadowOffsetX = 2; ctx.shadowOffsetY = 2; ctx.shadowBlur = 2; ctx.shadowColor = "rgba(0, 0, 0, 0.5)"; ctx.font = "20px Times New Roman"; ctx.fillStyle = "Black"; ctx.fillText("Sample String", 5, 30); }