??HTML5 添加的最受歡迎的功能就是<canvas>元素辫秧。這個元素負(fù)責(zé)在頁面中設(shè)定一個區(qū)域束倍,然后就可以通過 JavaScript 動態(tài)地在這個區(qū)域中繪制圖形。
??<canvas>元素最早是由蘋果公司推出的盟戏,當(dāng)時主要用在其 Dashboard 微件中绪妹。
??很快,HTML5 加入了這個元素柿究,主流瀏覽器也迅速開始支持它邮旷。IE9+、Firefox 1.5+蝇摸、Safari 2+婶肩、Opera 9+、Chrome貌夕、iOS 版 Safari 以及 Android 版 WebKit 都在某種程度上支持<canvas>狡孔。
??與瀏覽器環(huán)境中的其他組件類似,<canvas>由幾組 API 構(gòu)成蜂嗽,但并非所有瀏覽器都支持所有這些 API苗膝。除了具備基本繪圖能力的 2D 上下文,<canvas>還建議了一個名為 WebGL 的 3D 上下文植旧。
??目前辱揭,支持該元素的瀏覽器都支持 2D 上下文及文本 API,但對 WebGL 的支持還不夠好病附。由于 WebGL 還是實驗性的问窃,因此要得到所有瀏覽器支持還需要很長一段時間。Firefox 4+和 Chrome 支持 WebGL 規(guī)范的早期版本完沪,但一些老版本的操作系統(tǒng)域庇,比如 Windows XP,由于缺少必要的繪圖驅(qū)動程序覆积,即便安裝了這兩款瀏覽器也無濟(jì)于事听皿。
1、基本用法
??要使用 <canvas> 元素宽档,必須先設(shè)置 width 和 height 屬性尉姨,指定可以繪圖的區(qū)域大小。出現(xiàn)在開始和結(jié)束標(biāo)簽中的內(nèi)容是后備信息吗冤,如果瀏覽器不支持 <canvas> 元素又厉,就會顯示這些信息九府。下面就是 <canvas> 元素的例子。
<canvas id="drawing" width="200" height="200">A drawing of something.</canvas>
??與其它元素一樣覆致,<canvas> 元素對應(yīng)的 DOM 元素對象也有 width 和 height 屬性侄旬,可以隨意修改。而且煌妈,也能通過 CSS 為該元素添加樣式勾怒,如果不添加任何樣式或者不繪制任何圖形,在頁面中是看不到該元素的声旺。
??要在這塊畫布(canvas)上繪圖,需要取得繪圖上下文段只。而取得繪圖上下文對象的引用腮猖,需要調(diào)用 getContext() 方法并傳入上下文的名字。傳入"2d"赞枕,就可以取得 2D 上下文對象澈缺。
var drawing = document.getElementById('drawing');
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext) {
var context = drawing.getContext("2d");
// 更多代碼
}
??在使用 <canvas> 元素之前,首先要檢測 getContext() 方法是否存在炕婶,這一步非常重要姐赡。有些瀏覽器會為 HTML 規(guī)范之外的元素創(chuàng)建默認(rèn)的 HTML 元素對象(假設(shè)你想在 Firefox3 中使用 <canvas> 元素,雖然瀏覽器會為該標(biāo)簽創(chuàng)建一個 DOM 對象柠掂,但這個對象中并沒有 getContext() 方法项滑。)。在這種情況下涯贞,即使 drawing 變量中保存著一個有效的元素引用枪狂,也檢測不到 getContext() 方法。
??使用 toDataURL() 方法宋渔,可以導(dǎo)出在<canvas>元素上繪制的圖像州疾。這個方法接受一個參數(shù),即圖像的 MIME 類型格式皇拣,而且適合用于創(chuàng)建圖像的任何上下文严蓖。比如,要取得畫布中的一幅 PNG 格式的圖像氧急,可以使用以下代碼颗胡。
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext()) {
// 取得圖像的數(shù)據(jù) URI
var imgURI = drawing.toDataURL("image/png");
// 顯示圖像
var image = document.creatElement("img");
image.src = imgURI;
document.body.appendChild(image);
}
??默認(rèn)情況下,瀏覽器會將圖像編碼為 PNG 格式(除非另行指定)吩坝。Firefox 和 Opera 也支持基于 "image/jpeg" 參數(shù)的 JPEG 編碼格式杭措。由于這個方法是后來才追加的,所以支持<canvas>的瀏覽器也是在較新的版本中才加入了對它的支持钾恢,比如 IE9手素、Firefox3.5 和 Opera10鸳址。
如果繪制到畫布上的圖像源自不同的域犯戏,toDataURL() 方法會拋出錯誤与殃。
2擦酌、2D 上下文
??使用 2D 繪圖上下文提供的方法部蛇,可以繪制簡單的 2D 圖形瓢谢,比如矩形镐作、弧線和路徑廊鸥。
??2D 上下文的坐標(biāo)開始于<canvas>元素的左上角邦投,原點坐標(biāo)是(0,0)邓嘹。所有坐標(biāo)值都基于這個原點計算酣栈,x 值越大表示越靠右,y 值越大表示越靠下汹押。默認(rèn)情況下矿筝,width 和 height 表示水平和垂直兩個方向上可用的像素數(shù)目。
2.1棚贾、 填充和描邊
??2D 上下文的兩種基本繪圖操作是填充和描邊窖维。
??填充,就是用指定的樣式(顏色妙痹、漸變或圖像)填充圖形铸史;
??描邊,就是只在圖形的邊緣畫線怯伊。
??大多數(shù) 2D 上下文操作都會細(xì)分為填充和描邊兩個操作琳轿,而操作的結(jié)果取決于兩個屬性:fillStyle 和 strokeStyle。這兩個屬性的值可以是字符串耿芹、漸變對象或模式對象利赋,而且它們的默認(rèn)值是"#000000"。
??如果為它們指定表示顏色的字符串值猩系,可以使用 CSS 中指定顏色值的任何格式媚送,包括顏色名、十六進(jìn)制碼寇甸、rgb塘偎、rgba、hsl 或 hsla拿霉。示例:
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
context.strokeStyle = "red";
context.fillStyle = "#0000ff";
}
??以上代碼將 strokeStyle 設(shè)置為 red(CSS 中的顏色名)吟秩,將 fillStyle 設(shè)置為#0000ff(藍(lán)色)。
??然后绽淘,所有涉及描邊和填充的操作都將使用這兩個樣式涵防,直至重新設(shè)置這兩個值。如前所述沪铭,這兩個屬性的值也可以是漸變對象或模式對象壮池。本章后面會討論這兩種對象偏瓤。
2.2、 繪制矩形
??矩形是唯一一種可以直接在 2D 上下文中繪制的形狀椰憋。與矩形有關(guān)的方法包括 fillRect()、strokeRect() 和 clearRect()橙依。
??這三個方法都能接收 4 個參數(shù):矩形的 x 坐標(biāo)证舟、矩形的 y 坐標(biāo)、矩形寬度和矩形高度窗骑。這些參數(shù)的單位都是像素女责。
??首先,fillRect() 方法在畫布上繪制的矩形會填充指定的顏色创译。填充的顏色通過 fillStyle 屬性指定抵知,比如:
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 繪制半透明的藍(lán)色矩形
context.fillStyle = "rgba(0, 0, 255, .5)";
context.fillRect(30, 30, 50, 50);
}
??以上代碼首先將 fillStyle 設(shè)置為紅色,然后從(10, 10)處開始繪制矩形昔榴,矩形的寬和高均為 50 像素。然后碘橘,通過 rgba() 格式再將 fillStyle 設(shè)置為半透明的藍(lán)色互订,在第一個矩形上面繪制第二個矩形。結(jié)果就是可以透過藍(lán)色的矩形看到紅色的矩形痘拆。
??strokeRect() 方法在畫布上繪制的矩形會使用指定的顏色描邊仰禽。描邊顏色通過 strokeStyle 屬性指定。示例:
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
// 繪制紅色描邊矩形
context.strokeStyle= "#ff0000";
context.strokeRect(10, 10, 50, 50);
// 繪制半透明的藍(lán)色描邊矩形
context.strokeStyle= "rgba(0, 0, 255, .5)";
context.strokeRect(30, 30, 50, 50);
}
??以上代碼繪制了兩個重疊的矩形纺蛆。不過吐葵,這兩個矩形都只有框線,內(nèi)部并沒有填充顏色桥氏。
??描邊線條的寬度由 lineWidth 屬性控制温峭,該屬性的值可以是任意整數(shù)。
??另外字支,通過 lineCap 屬性可以控制線條末端的形狀是平頭凤藏、圓頭還是方頭("butt"、"round" 或 "square")堕伪。
??通過 lineJoin 屬性可以控制線條相交的方式是圓交揖庄、斜
交還是斜接("round"、"bevel" 或 "miter")欠雌。
??最后蹄梢,clearRect() 方法用于清除畫布上的矩形區(qū)域。本質(zhì)上富俄,這個方法可以把繪制上下文中的某一矩形區(qū)域變透明禁炒。通過繪制形狀然后再清除指定區(qū)域而咆,就可以生成有意思的效果,例如把某個形狀切掉一塊齐苛。示例:
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 繪制半透明的藍(lán)色矩形
context.fillStyle = "rgba(0, 0, 255, .5)";
context.fillRect(30, 30, 50, 50);
// 在兩個矩形重疊的地方清除一個小矩形
context.clearRect(40, 40, 10, 10);
}
2.3翘盖、繪制路徑
??2D 繪制上下文支持很多在畫布上繪制路徑的方法。通過路徑可以創(chuàng)造出復(fù)雜的形狀和線條凹蜂。
??要繪制路徑馍驯,首先必須調(diào)用 beginPath() 方法,表示要開始繪制新路徑玛痊。然后汰瘫,再通過調(diào)用下列方法來實際地繪制路徑。
- arc(x, y, radius, startAngle, endAngle, counterclockwise):以(x, y)為圓心繪制一條弧線擂煞,弧線半徑為 radius混弥,起始和結(jié)束角度(用弧度表示)分別為 startAngle 和 endAngle。最后一個參數(shù)表示 startAngle 和 endAngle 是否按逆時針方向計算对省,值為 false 表示按順時針方向計算蝗拿。
- arcTo(x1, y1, x2, y2, radius):從上一點開始繪制一條弧線,到(x2, y2)為止蒿涎,并且以給定的半徑 radius 穿過(x1, y1)哀托。
- bezierCurveTo(c1x, c1y, c2x, c2y, x, y):從上一點開始繪制一條曲線,到(x, y) 為止劳秋,并且以 (c1x, c1y) 和 (c2x, c2y) 為控制點仓手。
- lineTo(x, y):從上一點開始繪制一條直線,到(x, y)為止玻淑。
- moveTo(x, y):將繪圖游標(biāo)移動到(x, y)嗽冒,不畫線。
- quadraticCurveTo(cx, cy, x, y):從上一點開始繪制一條二次曲線补履,到(x, y)為止添坊,并且以(cx, cy)作為控制點。
- rect(x, y, width, height):從點(x, y)開始繪制一個矩形箫锤,寬度和高度分別由 width 和 height 指定帅腌。這個方法繪制的是矩形路徑,而不是 strokeRect() 和 fillRect() 所繪制的獨立的形狀麻汰。
??創(chuàng)建了路徑后速客,接下來有幾種可能的選擇。如果想繪制一條連接到路徑起點的線條五鲫,可以調(diào)用 closePath()溺职。
??如果路徑已經(jīng)完成,你想用 fillStyle 填充它,可以調(diào)用 fill() 方法浪耘。
??另外乱灵,還可以調(diào)用 stroke() 方法對路徑描邊,描邊使用的是 strokeStyle七冲。
??最后還可以調(diào)用 clip()痛倚,這個方法可以在路徑上創(chuàng)建一個剪切區(qū)域。
??下面看一個例子澜躺,即繪制一個不帶數(shù)字的時鐘表盤蝉稳。
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
// 開始路徑
context.beginPath();
// 繪制外圓
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
// 繪制內(nèi)圓
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
// 繪制分針
context.moveTo(100, 100);
context.lineTo(100, 15);
// 繪制時針
context.moveTo(100, 100);
context.lineTo(35, 100);
// 描邊路徑
context.stroke();
}
??上述例子使用 arc() 方法繪制了兩個圓形:一個外圓和一個內(nèi)圓,構(gòu)成了表盤的邊框掘鄙。外圓的半徑是 99 像素耘戚,圓心位于點(100,100),也是畫布的中心點操漠。為了繪制一個完整的圓形收津,我們從 0 弧度開始,繪制 2π 弧度(通過 Math.PI 來計算)浊伙。
??在繪制內(nèi)圓之前撞秋,必須把路徑移動到內(nèi)圓上的某一點,以避免繪制出多余的線條嚣鄙。
??第二次調(diào)用 arc() 使用了小一點的半徑吻贿,以便創(chuàng)造邊框的效果。
??然后拗慨,組合使用 moveTo() 和 lineTo() 方法來繪制時針和分針廓八。
??最后一步是調(diào)用 stroke() 方法奉芦,這樣才能把圖形繪制到畫布上赵抢,如下圖所示:
??在 2D 繪圖上下文中,路徑是一種主要的繪圖方式声功,因為路徑能為要繪制的圖形提供更多控制烦却。
??由于路徑的使用很頻繁,所以就有了一個名為 isPointInPath() 的方法先巴。這個方法接收 x 和 y 坐標(biāo)作為參數(shù)其爵,用于在路徑被關(guān)閉之前確定畫布上的某一點是否位于路徑上,例如:
if (context.isPointInPath(100, 100)){
alert("Point (100, 100) is in the path.");
}
??2D 上下文中的路徑 API 已經(jīng)非常穩(wěn)定伸蚯,可以利用它們結(jié)合不同的填充和描邊樣式摩渺,繪制出非常復(fù)雜的圖形來。
2.4剂邮、 繪制文本
??文本與圖形總是如影隨形摇幻。為此,2D 繪圖上下文也提供了繪制文本的方法。
??繪制文本主要有兩個方法:fillText() 和 strokeText()绰姻。這兩個方法都可以接收 4 個參數(shù):要繪制的文本字符串枉侧、x 坐標(biāo)、y 坐標(biāo)和可選的最大像素寬度狂芋。而且榨馁,這兩個方法都以下列 3 個屬性為基礎(chǔ)。
- font:表示文本樣式帜矾、大小及字體翼虫,用 CSS 中指定字體的格式來指定,例如"10px Arial"黍特。
- textAlign:表示文本對齊方式蛙讥。可能的值有 "start"灭衷、"end"次慢、"left"、"right" 和 "center"翔曲。建議使用"start"和"end"迫像,不要使用"left"和"right",因為前兩者的意思更穩(wěn)妥瞳遍,能同時適合從左到右和從右到左顯示(閱讀)的語言闻妓。
- textBaseline:表示文本的基線÷有担可能的值有 "top"由缆、"hanging"、"middle"猾蒂、 "alphabetic"均唉、"ideographic" 和 "bottom"。
??這幾個屬性都有默認(rèn)值肚菠,因此沒有必要每次使用它們都重新設(shè)置一遍值舔箭。
??fillText() 方法使用 fillStyle 屬性繪制文本,而 strokeText() 方法使用 strokeStyle 屬性為文本描邊蚊逢。
??相對來說层扶,還是使用 fillText() 的時候更多,因為該方法模仿了在網(wǎng)頁中正常顯示文本烙荷。例如镜会,下面的代碼在前一節(jié)創(chuàng)建的表盤上方繪制了數(shù)字 12:
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);
??因為這里把 textAlign 設(shè)置為"center",把 textBaseline 設(shè)置為"middle"终抽,所以坐標(biāo)(100, 20) 表示的是文本水平和垂直中點的坐標(biāo)戳表。
??如果將 textAlign 設(shè)置為"start"焰薄,則 x 坐標(biāo)表示的是文本左端的位置(從左到右閱讀的語言);設(shè)置為"end"扒袖,則 x 坐標(biāo)表示的是文本右端的位置(從左到右閱讀的
語言)塞茅。例如:
// 正常
context.font = "bold 14px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillText("12", 100, 20);
// 起點對齊
context.textAlign = "start";
context.fillText("12", 100, 40);
// 終點對齊
context.textAlign = "end";
context.fillText("12", 100, 60);
??這一回繪制了三個字符串"12",每個字符串的 x 坐標(biāo)值相同季率,但 textAlign 值不同野瘦。另外,后兩個字符串的 y 坐標(biāo)依次增大飒泻,以避免相互重疊鞭光。結(jié)果如下圖所示:
??表盤中的分針恰好位于正中間,因此文本的水平對齊方式如何變化也能夠一目了然泞遗。
??類似地惰许,修改 textBaseline 屬性的值可以調(diào)整文本的垂直對齊方式:值為"top",y 坐標(biāo)表示文本頂端史辙;值為 "bottom"汹买,y 坐標(biāo)表示文本底端;值為 "hanging"聊倔、"alphabetic" 和 "ideographic"晦毙,則 y 坐標(biāo)分別指向字體的特定基線坐標(biāo)。
??由于繪制文本比較復(fù)雜耙蔑,特別是需要把文本控制在某一區(qū)域中的時候见妒,2D 上下文提供了輔助確定文本大小的方法 measureText()。
??這個方法接收一個參數(shù)甸陌,即要繪制的文本须揣;返回一個 TextMetrics 對象。返回的對象目前只有一個 width 屬性钱豁,但將來還會增加更多度量屬性耻卡。
??measureText() 方法利用 font、textAlign 和 textBaseline 的當(dāng)前值計算指定文本的大小寥院。
??比如劲赠,假設(shè)你想在一個 140 像素寬的矩形區(qū)域中繪制文本 Hello world!涛目,下面的代碼從 100 像素的字體大小開始遞減秸谢,最終會找到合適的字體大小。
var fontSize = 100;
context.font = fontSize + "px Arial";
while(context.measureText("Hello world!").width > 140){
fontSize--;
context.font = fontSize + "px Arial";
}
context.fillText("Hello world!", 10, 10);
context.fillText("Font size is " + fontSize + "px", 10, 50);
??前面提到過霹肝,fillText 和 strokeText() 方法都可以接收第四個參數(shù)估蹄,也就是文本的最大像素寬度。不過沫换,這個可選的參數(shù)尚未得到所有瀏覽器支持(最早支持它的是 Firefox 4)臭蚁。
??提供這個參數(shù)后,調(diào)用 fillText() 或 strokeText() 時如果傳入的字符串大于最大寬度,則繪制的文本字符的高度正確垮兑,但寬度會收縮以適應(yīng)最大寬度冷尉。
??繪制文本還是相對比較復(fù)雜的操作,因此支持<canvas>元素的瀏覽器也并未完全實現(xiàn)所有與繪制文本相關(guān)的 API系枪。
2.5雀哨、變換
??通過上下文的變換,可以把處理后的圖像繪制到畫布上私爷。2D 繪制上下文支持各種基本的繪制變換雾棺。
??創(chuàng)建繪制上下文時,會以默認(rèn)值初始化變換矩陣衬浑,在默認(rèn)的變換矩陣下捌浩,所有處理都按描述直接繪制。
??為繪制上下文應(yīng)用變換工秩,會導(dǎo)致使用不同的變換矩陣應(yīng)用處理尸饺,從而產(chǎn)生不同的結(jié)果。
??可以通過如下方法來修改變換矩陣助币。
- rotate(angle):圍繞原點旋轉(zhuǎn)圖像 angle 弧度侵佃。
- scale(scaleX, scaleY):縮放圖像,在 x 方向乘以 scaleX奠支,在 y 方向乘以 scaleY馋辈。scaleX 和 scaleY 的默認(rèn)值都是 1.0。
- translate(x, y):將坐標(biāo)原點移動到(x,y)倍谜。執(zhí)行這個變換之后迈螟,坐標(biāo)(0,0)會變成之前由(x, y)表示的點。
- transform(m1_1, m1_2, m2_1, m2_2, dx, dy):直接修改變換矩陣,方式是乘以如下矩陣载弄。
m1_1??m1_2??dx
m2_1??m2_2??dy
0????0????1 - setTransform(m1_1, m1_2, m2_1, m2_2, dx, dy):將變換矩陣重置為默認(rèn)狀態(tài)嘉涌,然后再調(diào)用 transform()。
??變換有可能很簡單损敷,但也可能很復(fù)雜,這都要視情況而定堤尾。比如,就拿前面例子中繪制表針來說舱殿,如果把原點變換到表盤的中心刺彩,然后再繪制表針就容易多了迷郑。請看下面的例子枝恋。
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
// 開始路徑
context.beginPath();
// 繪制外圓
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
// 繪制內(nèi)圓
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
// 變換原點
context.translate(100, 100);
// 繪制分針
context.moveTo(0,0);
context.lineTo(0, -85);
// 繪制時針
context.moveTo(0, 0);
context.lineTo(-65, 0);
// 描邊路徑
context.stroke();
}
??把原點變換到時鐘表盤的中心點(100, 100)后,在同一方向上繪制線條就變成了簡單的數(shù)學(xué)問題了嗡害。所有數(shù)學(xué)計算都基于(0, 0)焚碌,而不是(100, 100)。
??還可以更進(jìn)一步霸妹,像下面這樣使用 rotate() 方法旋轉(zhuǎn)時鐘的表針十电。
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d");
// 開始路徑
context.beginPath();
// 繪制外圓
context.arc(100, 100, 99, 0, 2 * Math.PI, false);
// 繪制內(nèi)圓
context.moveTo(194, 100);
context.arc(100, 100, 94, 0, 2 * Math.PI, false);
// 變換原點
context.translate(100, 100);
// 旋轉(zhuǎn)表針
context.rotate(1);
// 繪制分針
context.moveTo(0,0);
context.lineTo(0, -85);
// 繪制時針
context.moveTo(0, 0);
context.lineTo(-65, 0);
// 描邊路徑
context.stroke();
}
??因為原點已經(jīng)變換到了時鐘表盤的中心點,所以旋轉(zhuǎn)也是以該點為圓心的叹螟。結(jié)果就像是表針真地被固定在表盤中心一樣鹃骂,然后向右旋轉(zhuǎn)了一定角度。
??無論是剛才執(zhí)行的變換罢绽,還是 fillStyle畏线、strokeStyle 等屬性,都會在當(dāng)前上下文中一直有效良价,除非再對上下文進(jìn)行什么修改寝殴。
??雖然沒有什么辦法把上下文中的一切都重置回默認(rèn)值,但有兩個方法可以跟蹤上下文的狀態(tài)變化明垢。如果你知道將來還要返回某組屬性與變換的組合蚣常,可以調(diào)用 save() 方法。
??調(diào)用這個方法后痊银,當(dāng)時的所有設(shè)置都會進(jìn)入一個棧結(jié)構(gòu)抵蚊,得以妥善保管。然后可以對上下文進(jìn)行其他修改溯革。等想要回到之前保存的設(shè)置時泌射,可以調(diào)用 restore() 方法,在保存設(shè)置的棧結(jié)構(gòu)中向前返回一級鬓照,
恢復(fù)之前的狀態(tài)熔酷。
??連續(xù)調(diào)用 save() 可以把更多設(shè)置保存到棧結(jié)構(gòu)中,之后再連續(xù)調(diào)用 restore() 則可以一級一級返回豺裆。下面來看一個例子拒秘。
context.fillStyle = "#ff0000";
context.save();
context.fillStyle = "#00ff00";
context.translate(100, 100);
context.save();
context.fillStyle = "#0000ff";
context.fillRect(0, 0, 100, 200); // 從點(100,100)開始繪制藍(lán)色矩形
context.restore();
context.fillRect(10, 10, 100, 200); // 從點(110,110)開始繪制綠色矩形
context.restore();
context.fillRect(0, 0, 100, 200); // 從點(0,0)開始繪制紅色矩形
??首先,將 fillStyle 設(shè)置為紅色臭猜,并調(diào)用 save() 保存上下文狀態(tài)躺酒。
??接下來,把 fillStyle 修改為綠色蔑歌,把坐標(biāo)原點變換到(100, 100)羹应,再調(diào)用 save() 保存上下文狀態(tài)。
??然后次屠,把 fillStyle 修改為藍(lán)色并繪制藍(lán)色的矩形园匹。因為此時的坐標(biāo)原點已經(jīng)變了雳刺,所以矩形的左上角坐標(biāo)實際上是(100, 100)。
??然后調(diào)用 restore()裸违,之后 fillStyle 變回了綠色掖桦,因而第二個矩形就是綠色。之所以第二個矩形的起點坐標(biāo)是(110, 110)供汛,是因為坐標(biāo)位置的變換仍然起作用枪汪。
??再調(diào)用一次 restore(),變換就被取消了怔昨,而 fillStyle 也返回了紅色雀久。所以最后一個矩形是紅色的,而且繪制的起點是(0,0)趁舀。
??需要注意的是岸啡,save() 方法保存的只是對繪圖上下文的設(shè)置和變換,不會保存繪圖上下文的內(nèi)容赫编。
2.6巡蘸、 繪制圖像
?? 繪圖上下文內(nèi)置了對圖像的支持。如果你想把一幅圖像繪制到畫布上擂送,可以使用 drawImage() 方法悦荒。根據(jù)期望的最終結(jié)果不同,調(diào)用這個方法時嘹吨,可以使用三種不同的參數(shù)組合搬味。最簡單的調(diào)用方式是傳入一個 HTML <img> 元素,以及繪制該圖像的起點的 x 和 y 坐標(biāo)蟀拷。例如:
var image = document.images[0];
context.drawImage(image, 10, 10);
??這兩行代碼取得了文檔中的第一幅圖像碰纬,然后將它繪制到上下文中,起點為(10, 10)问芬。繪制到畫布上的圖像大小與原始大小一樣悦析。
??如果你想改變繪制后圖像的大小,可以再多傳入兩個參數(shù)此衅,分別表示目標(biāo)寬度和目標(biāo)高度强戴。通過這種方式來縮放圖像并不影響上下文的變換矩陣。例如:
context.drawImage(image, 50, 10, 20, 30);
??執(zhí)行代碼后挡鞍,繪制出來的圖像大小會變成 20×30 像素骑歹。
??除了上述兩種方式,還可以選擇把圖像中的某個區(qū)域繪制到上下文中墨微。
??drawImage() 方法的這種調(diào)用方式總共需要傳入 9 個參數(shù):要繪制的圖像道媚、源圖像的 x 坐標(biāo)、源圖像的 y 坐標(biāo)、源圖像的寬度最域、源圖像的高度谴分、目標(biāo)圖像的 x 坐標(biāo)、目標(biāo)圖像的 y 坐標(biāo)羡宙、目標(biāo)圖像的寬度狸剃、目標(biāo)圖像的高度掐隐。這樣調(diào)用 drawImage() 方法可以獲得最多的控制狗热。例如:
context.drawImage(image, 0, 10, 50, 50, 0, 100, 40, 60);
??這行代碼只會把原始圖像的一部分繪制到畫布上。原始圖像的這一部分的起點為(0, 10)虑省,寬和高都是 50 像素匿刮。最終繪制到上下文中的圖像的起點是(0, 100),而大小變成了 40×60 像素探颈。
??這種調(diào)用方式可以創(chuàng)造出很有意思的效果熟丸,如下圖所示:
??除了給 drawImage() 方法傳入 HTML <img> 元素外,還可以傳入另一個<canvas>元素作為其第一個參數(shù)伪节。這樣光羞,就可以把另一個畫布內(nèi)容繪制到當(dāng)前畫布上。
??結(jié)合使用 drawImage() 和其他方法怀大,可以對圖像進(jìn)行各種基本操作纱兑。而操作的結(jié)果可以通過 toDataURL() 方法獲得。(請讀者注意化借,雖然本章至今一直在討論 2D 繪圖上下文潜慎,但 toDataURL()是 Canvas 對象的方法,不是上下文對象的方法蓖康。)
??不過铐炫,有一個例外,即圖像不能來自其他域蒜焊。如果圖像來自其他域倒信,調(diào)用 toDataURL() 會拋出一個錯誤。打個比方泳梆,假如位于www.example.com 上的頁面繪制的圖像來自于 www.wrox.com堤结,那當(dāng)前上下文就會被認(rèn)為“不干凈”,因而會拋出錯誤鸭丛。
2.7竞穷、陰影
??2D 上下文會根據(jù)以下幾個屬性的值,自動為形狀或路徑繪制出陰影鳞溉。
- shadowColor:用 CSS 顏色格式表示的陰影顏色瘾带,默認(rèn)為黑色。
- shadowOffsetX:形狀或路徑 x 軸方向的陰影偏移量熟菲,默認(rèn)為 0看政。
- shadowOffsetY:形狀或路徑 y 軸方向的陰影偏移量朴恳,默認(rèn)為 0。
- shadowBlur:模糊的像素數(shù)允蚣,默認(rèn) 0于颖,即不模糊。
??這些屬性都可以通過 context 對象來修改嚷兔。只要在繪制前為它們設(shè)置適當(dāng)?shù)闹瞪ィ湍茏詣赢a(chǎn)生陰影。例如:
var context = drawing.getContext("2d");
// 設(shè)置陰影
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 4;
context.shadowColor = "rgba(0, 0, 0, 0.5)";
// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 繪制藍(lán)色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);
??兩個矩形的陰影樣式相同冒晰,如下圖所示:
??不同瀏覽器對陰影的支持有一些差異同衣。IE9、Firefox 4 和 Opera 11 的行為最為規(guī)范壶运,其他瀏覽器多多少少會有一些奇怪的現(xiàn)象耐齐,甚至根本不支持陰影。
??Chrome(直至第 10 版)不能正確地為描邊的形狀應(yīng)用實心陰影蒋情。Chrome 和 Safari(直至第 5 版)在為帶透明像素的圖像應(yīng)用陰影時也會有問題:不透明部分的下方本來是該有陰影的埠况,但此時則一概不見了。Safari 也不能給漸變圖形應(yīng)用陰影棵癣,其他瀏覽器都可以辕翰。
2.8、漸變
??漸變由 CanvasGradient 實例表示浙巫,很容易通過 2D 上下文來創(chuàng)建和修改金蜀。要創(chuàng)建一個新的線性漸變,可以調(diào)用 createLinearGradient() 方法的畴。這個方法接收 4 個參數(shù):起點的 x 坐標(biāo)渊抄、起點的 y 坐標(biāo)、終點的 x 坐標(biāo)丧裁、終點的 y 坐標(biāo)护桦。
??調(diào)用這個方法后,它就會創(chuàng)建一個指定大小的漸變煎娇,并返回 CanvasGradient 對象的實例二庵。
??創(chuàng)建了漸變對象后,下一步就是使用 addColorStop() 方法來指定色標(biāo)缓呛。這個方法接收兩個參數(shù):色標(biāo)位置和 CSS 顏色值催享。色標(biāo)位置是一個 0(開始的顏色)到 1(結(jié)束的顏色)之間的數(shù)字。例如:
var gradient = context.createLinearGradient(30, 30, 70, 70);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
??此時哟绊,gradient 對象表示的是一個從畫布上點(30, 30)到點(70, 70)的漸變因妙。起點的色標(biāo)是白色,終點的色標(biāo)是黑色。然后就可以把 fillStyle 或 strokeStyle 設(shè)置為這個對象攀涵,從而使用漸變來繪制形狀或描邊:
// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 繪制漸變矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
??為了讓漸變覆蓋整個矩形铣耘,而不是僅應(yīng)用到矩形的一部分,矩形和漸變對象的坐標(biāo)必須匹配才行以故。以上代碼會得到如下圖所示的結(jié)果:
??如果沒有把矩形繪制到恰當(dāng)?shù)奈恢梦舷福强赡芫椭粫@示部分漸變效果。例如:
context.fillStyle = gradient;
context.fillRect(50, 50, 50, 50);
??這兩行代碼執(zhí)行后得到的矩形只有左上角稍微有一點白色怒详。這主要是因為矩形的起點位于漸變的中間位置炉媒,而此時漸變差不多已經(jīng)結(jié)束了。由于漸變不重復(fù)棘利,所以矩形的大部分區(qū)域都是黑色橱野。如下圖所示:
??確保漸變與形狀對齊非常重要朽缴,有時候可以考慮使用函數(shù)來確保坐標(biāo)合適善玫。例如:
function createRectLinearGradient(context, x, y, width, height){
return context.createLinearGradient(x, y, x + width, y + height);
}
??這個函數(shù)基于起點的 x 和 y 坐標(biāo)以及寬度和高度值來創(chuàng)建漸變對象,從而讓我們可以在 fillRect() 中使用相同的值密强。
var gradient = createRectLinearGradient(context, 30, 30, 50, 50);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
//繪制漸變矩形
context.fi llStyle = gradient;
context.fillRect(30, 30, 50, 50);
??使用畫布的時候茅郎,確保坐標(biāo)匹配很重要,也需要一些技巧或渤。類似 createRectLinearGradient() 這樣的輔助方法可以讓控制坐標(biāo)更容易一些系冗。
??要創(chuàng)建徑向漸變(或放射漸變),可以使用 createRadialGradient() 方法薪鹦。這個方法接收 6 個參數(shù)掌敬,對應(yīng)著兩個圓的圓心和半徑。前三個參數(shù)指定的是起點圓的原心(x 和 y)及半徑池磁,后三個參數(shù)指定的是終點圓的原心(x 和 y)及半徑奔害。
??可以把徑向漸變想象成一個長圓桶,而這 6 個參數(shù)定義的正是這個桶的兩個圓形開口的位置地熄。如果把一個圓形開口定義得比另一個小一些华临,那這個圓桶就變成了圓錐體,而通過移動每個圓形開口的位置端考,就可達(dá)到像旋轉(zhuǎn)這個圓錐體一樣的效果雅潭。
??如果想從某個形狀的中心點開始創(chuàng)建一個向外擴(kuò)散的徑向漸變效果,就要將兩個圓定義為同心圓却特。
??比如扶供,就拿前面創(chuàng)建的矩形來說,徑向漸變的兩個圓的圓心都應(yīng)該在(55, 55)裂明,因為矩形的區(qū)域是從(30, 30)到(80,80)椿浓。請看代碼:
var gradient = context.createRadialGradient(55, 55, 10, 55, 55, 30);
gradient.addColorStop(0, "white");
gradient.addColorStop(1, "black");
//繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
//繪制漸變矩形
context.fillStyle = gradient;
context.fillRect(30, 30, 50, 50);
??運行代碼,會得到如下圖所示的結(jié)果。
??因為創(chuàng)建比較麻煩轰绵,所以徑向漸變并不那么容易控制粉寞。不過,一般來說左腔,讓起點圓和終點圓保持為同心圓的情況比較多唧垦,這時候只要考慮給兩個圓設(shè)置不同的半徑就好了。
2.9液样、模式
??模式其實就是重復(fù)的圖像振亮,可以用來填充或描邊圖形。要創(chuàng)建一個新模式鞭莽,可以調(diào)用 createPattern() 方法并傳入兩個參數(shù):一個 HTML <img> 元素和一個表示如何重復(fù)圖像的字符串坊秸。
??其中,第二個參數(shù)的值與 CSS 的background-repeat 屬性值相同澎怒,包括"repeat"褒搔、"repeat-x"、"repeat-y" 和 "no-repeat"。示例:
var image = document.images[0],
pattern = context.createPattern(image, "repeat");
// 繪制矩形
context.fillStyle = pattern;
context.fillRect(10, 10, 150, 150);
??需要注意的是,模式與漸變一樣逆巍,都是從畫布的原點(0, 0)開始的。
??將填充樣式(fillStyle)設(shè)置為模式對象琳状,只表示在某個特定的區(qū)域內(nèi)顯示重復(fù)的圖像,而不是要從某個位置開始繪制重復(fù)的圖像盒齿。
??createPattern() 方法的第一個參數(shù)也可以是一個<video>元素念逞,或者另一個<canvas>元素。
2.10边翁、使用圖像數(shù)據(jù)
??2D 上下文的一個明顯的長處就是翎承,可以通過 getImageData() 取得原始圖像數(shù)據(jù)。
??這個方法接收 4 個參數(shù):要取得其數(shù)據(jù)的畫面區(qū)域的 x 和 y 坐標(biāo)以及該區(qū)域的像素寬度和高度倒彰。
??示例审洞,要取得左上角坐標(biāo)為(10, 5)、大小為 50x50 像素的區(qū)域的圖像數(shù)據(jù)待讳,可以使用以下代碼:
var imageData = context.getImageData(10, 5, 50, 50);
??上述返回的對象是 ImageData 的實例芒澜。
??每個 ImageData 對象都有三個屬性:width、height 和 data创淡。
??其中 data 屬性是一個數(shù)組痴晦,保存著圖像中每一個像素的數(shù)據(jù)。在 data 數(shù)組中琳彩,每一個像素用 4 個元素來保存誊酌,分別表示紅部凑、綠、藍(lán)和透明度碧浊。因此涂邀,第一個像素的數(shù)據(jù)就保存在數(shù)組的第 0 到第 3 個元素中,示例:
var data = imageData,
red = data[0],
green = data[1],
blue = data[2],
alpha = data[3];
??數(shù)組中的每個元素的值都介于 0 到 255 之間(包括 0 和 255)箱锐。能夠直接訪問到原始圖像數(shù)據(jù)比勉,就能夠以各種方式來操作這些數(shù)據(jù)。
??例如驹止,通過修改圖像數(shù)據(jù)浩聋,可以像下面這樣創(chuàng)建一個簡單的灰階過濾器。
var drawing = document.getElementById("drawing");
// 確定瀏覽器支持<canvas>元素
if (drawing.getContext){
var context = drawing.getContext("2d"),
image = document.images[0],
imageData, data,
i, len, average,
red, green, blue, alpha;
// 繪制原始圖像
context.drawImage(image, 0, 0);
// 取得圖像數(shù)據(jù)
imageData = context.getImageData(0, 0, image.width, image.height);
data = imageData.data;
for (i=0, len=data.length; i < len; i+=4){
red = data[i];
green = data[i+1];
blue = data[i+2];
alpha = data[i+3];
// 求得 rgb 平均值
average = Math.floor((red + green + blue) / 3);
// 設(shè)置顏色值臊恋,透明度不變
data[i] = average;
data[i+1] = average;
data[i+2] = average;
}
// 回寫圖像數(shù)據(jù)并顯示結(jié)果
imageData.data = data;
context.putImageData(imageData, 0, 0);
}
??上述例子首先在畫面上繪制了一幅圖像衣洁,然后取得了原始圖像數(shù)據(jù)。其中的 for 循環(huán)遍歷了圖像數(shù)據(jù)中的每一個像素抖仅。這里要注意的是坊夫,每次循環(huán)控制變量 i 都遞增 4。
??在取得每個像素的紅岸售、綠践樱、藍(lán)顏色值后厂画,計算出它們的平均值凸丸。再把這個平均值設(shè)置為每個顏色的值,結(jié)果就是去掉了每個像素的顏色袱院,只保留了亮度接近的灰度值(即彩色變黑白)屎慢。
??在把 data 數(shù)組回寫到 imageData 對象后,調(diào)用putImageData() 方法把圖像數(shù)據(jù)繪制到畫布上忽洛。最終得到了圖像的黑白版腻惠。
??當(dāng)然,通過操作原始像素值不僅能實現(xiàn)灰階過濾欲虚,還能實現(xiàn)其他功能集灌。
??只有在畫布“干凈”的情況下(即圖像并非來自其他域),才可以取得圖像數(shù)據(jù)复哆。如果畫布“不干凈”欣喧,那么訪問圖像數(shù)據(jù)時會導(dǎo)致 JavaScript 錯誤。
2.11梯找、合成
??還有兩個會應(yīng)用到 2D 上下文中所有繪制操作的屬性:globalAlpha 和 globalCompositionOperation唆阿。
??其中,globalAlpha 是一個介于 0 和 1 之間的值(包括 0 和 1)锈锤,用于指定所有繪制的透明度驯鳖。默認(rèn)值為 0闲询。如果所有后續(xù)操作都要基于相同的透明度,就可以先把 globalAlpha 設(shè)置為適當(dāng)值浅辙,然后繪制扭弧,最后再把它設(shè)置回默認(rèn)值 0。示例:
// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 修改全局透明度
context.globalAlpha = 0.5;
// 繪制藍(lán)色矩形
context.fillStyle = "rgba(0, 0, 255, 1)";
context.fillRect(30, 30, 50, 50);
// 重置全局透明度
context.globalAlpha = 0;
??在上述例子中记舆,我們把藍(lán)色矩形繪制到了紅色矩形上面寄狼。因為在繪制藍(lán)色矩形前,globalAlpha 已經(jīng)被設(shè)置為 0.5氨淌,所以藍(lán)色矩形會呈現(xiàn)半透明效果泊愧,透過它可以看到下面的紅色矩形。
??第二個屬性 globalCompositionOperation 表示后繪制的圖形怎樣與先繪制的圖形結(jié)合盛正。這個屬性的值是字符串删咱,可能的值如下。
- source-over(默認(rèn)值):后繪制的圖形位于先繪制的圖形上方豪筝。
- source-in:后繪制的圖形與先繪制的圖形重疊的部分可見痰滋,兩者其他部分完全透明。
- source-out:后繪制的圖形與先繪制的圖形不重疊的部分可見续崖,先繪制的圖形完全透明敲街。
- source-atop:后繪制的圖形與先繪制的圖形重疊的部分可見,先繪制圖形不受影響严望。
- destination-over:后繪制的圖形位于先繪制的圖形下方多艇,只有之前透明像素下的部分才可見。
- destination-in:后繪制的圖形位于先繪制的圖形下方像吻,兩者不重疊的部分完全透明峻黍。
- destination-out:后繪制的圖形擦除與先繪制的圖形重疊的部分。
- destination-atop:后繪制的圖形位于先繪制的圖形下方拨匆,在兩者不重疊的地方姆涩,先繪制的圖形會變透明。
- lighter:后繪制的圖形與先繪制的圖形重疊部分的值相加惭每,使該部分變亮骨饿。
- copy:后繪制的圖形完全替代與之重疊的先繪制圖形。
- xor:后繪制的圖形與先繪制的圖形重疊的部分執(zhí)行“異或”操作台腥。
??這個合成操作實際上用語言或者黑白圖像是很難說清楚的宏赘。要了解每個操作的具體效果,請參見 https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html览爵。推薦使用 IE9+或 Firefox 4+訪問前面的網(wǎng)頁置鼻,因為這兩款瀏覽器對 Canvas 的實現(xiàn)最完善。下面來看一個例子蜓竹。
// 繪制紅色矩形
context.fillStyle = "#ff0000";
context.fillRect(10, 10, 50, 50);
// 設(shè)置合成操作
context.globalCompositeOperation = "destination-over";
// 繪制藍(lán)色矩形
context.fillStyle = "rgba(0,0,255,1)";
context.fillRect(30, 30, 50, 50);
??如果不修改 globalCompositionOperation箕母,那么藍(lán)色矩形應(yīng)該位于紅色矩形之上储藐。但把 globalCompositionOperation 設(shè)置為"destination-over"之后,紅色矩形跑到了藍(lán)色矩形上面嘶是。
??在使用 globalCompositionOperation 的情況下钙勃,一定要多測試一些瀏覽器。因為不同瀏覽器對這個屬性的實現(xiàn)仍然存在較大的差別聂喇。Safari 和 Chrome 在這方面還有問題辖源,至于有什么問題,大家可以比較在打開上述頁面的情況下希太,IE9+和 Firefox 4+與它們有什么差異克饶。
小結(jié)
??HTML5 的<canvas>元素提供了一組 JavaScript API,讓我們可以動態(tài)地創(chuàng)建圖形和圖像誊辉。圖形是在一個特定的上下文中創(chuàng)建的矾湃,而上下文對象目前有兩種。第一種是 2D 上下文堕澄,可以執(zhí)行原始的繪圖操作邀跃,比如:
- 設(shè)置填充、描邊顏色和模式
- 繪制矩形
- 繪制路徑
- 繪制文本
- 創(chuàng)建漸變和模式
??第二種是 3D 上下文蛙紫,即 WebGL 上下文拍屑。WebGL 是從 OpenGL ES 2.0 移植到瀏覽器中的,而 OpenGLES 2.0 是游戲開發(fā)人員在創(chuàng)建計算機(jī)圖形圖像時經(jīng)常使用的一種語言坑傅。WebGL 支持比 2D 上下文更豐富和更強(qiáng)大的圖形圖像處理能力僵驰,比如:
- 用 GLSL(OpenGL Shading Language,OpenGL 著色語言)編寫的頂點和片段著色器
- 支持類型化數(shù)組裁蚁,即能夠?qū)?shù)組中的數(shù)據(jù)限定為某種特定的數(shù)值類型
- 創(chuàng)建和操作紋理
??目前矢渊,主流瀏覽器的較新版本大都已經(jīng)支持<canvas>標(biāo)簽。同樣地枉证,這些版本的瀏覽器基本上也都支持 2D 上下文。但對于 WebGL 而言移必,目前還只有 Firefox 4+和 Chrome 支持它室谚。