新的 HTML5 規范旨在幫助開發人員更輕松的編寫出各類 Web 應用,以順應當前 SaaS,云計算以及 RIA 等技術的最新趨勢。在 HTML5 得以廣泛推廣之前,開發人員通常使用 SVG,VML 等技術進行 Web 繪圖操作,但這些基于 XML 的繪圖語言聲明式的繪圖方式并不能滿足復雜繪圖操作在性能上的需求,比如 Web 游戲所需要的像素級別的繪圖能力。HTML5 canvas 元素的出現填補了這種不足,開發人員可以使用 JavaScript 腳本語言在 canvas 中進行一系列基于命令的圖形繪制操作,本文將通過講解如何使用 canvas 元素進行基本繪圖操作,以及完成簡單的動畫和用戶交互任務,闡明 canvas 在幫助構建 Web 圖形類應用時所能夠提供的能力。
?
背景介紹
HTML5 中新引入的 canvas 元素使得 Web 開發人員在無須借助任何第三方插件(如 Flash,Silverlight)的情況下,可以直接使用 JavaScript 腳本在 Web 頁面進行繪圖。它首次由蘋果公司的 Webkit 框架引入實現,并成功運用在 Safari 瀏覽器中,讀者在 這里 可 以體驗到基于 canvas 的精彩示例。目前,canvas 已成為 HTML5 規范中的事實性標準,并且已經被 Firefox 3.0+, Safari 3.0+, Chrome 3.0+, Opera10.0+ 等瀏覽器所支持。最近(本文撰寫之時),IE 也正式宣稱將在其 9.0 版本之后,開始對 canvas 元素進行支持。
基于 canvas 的繪圖填補了 SVG 繪圖的在復雜繪圖操作,特別是性能方面的不足,可廣泛應用于 Dashboard,2D/3D Game 等 Web 應用中。
?
基本繪圖 API
在 了解了什么是 canvas 元素之后,是時候使用 canvas 在 Web 頁面上真正進行的繪圖操作了。實際上,單獨的一個 canvas 標記只是在頁面中定義了一塊矩形區域,并無特別之處,開發人員只有配合使用 JavaScript 腳本,才能夠完成各種圖形,線條,以及復雜的圖形變換操作,與基于 SVG 來實現同樣繪圖效果來比較,canvas 繪圖是一種像素級別的位圖繪圖技術,而 SVG 則是一種矢量繪圖技術。正鑒于這種本質機理的不同,如何更快速高效的進行 canvas 渲染成為各主流 JavaScript 執行引擎性能比拼的重要指標之一。目前,Chrome 的 V8, Firefox 的 SpiderMonkey 以及 Safari 的 Nitro 等引擎都已經能夠很好的滿足二維繪圖所需的必要性能指標,雖然在運行一些基于 canvas 的游戲時 CPU 占用率還是相對較高,但我們有理由相信隨著 NVIDIA 和 AMD 等一系列硬件廠商的參與,硬件加速技術將大大提升 Web 應用的性能。
?
在開始繪圖之前,我們需要首先創建一個指定大小的 canvas,并為其指定一個 id,方便在 JavaScript 腳本中獲取該 DOM 實例對象。聲明一個 canvas 節點的方式如下所示。
<canvas id="canvas" width="300" height="200"> Fallback content, in case the browser does not support Canvas. </canvas>
?
需要指明的是,由于無法保證所有用戶使用的瀏覽器都能夠支持 canvas 元素,所以在目前開發基于 canvas 的 Web 應用中需要增加“Fallback content”,以提示用戶他們無法正常體驗此功能的原因或建議他們去下載最新的瀏覽器。
?
這里,好奇的讀者可能會問,既然這是一個普通的 DOM 節點,那么便意味著可以通過直接改變其 width 或 height 屬性值來改變 canvas 的大小?確實如此,但是,正如之前提到的 canvas 是一種像素級別的繪圖方法,因而,一旦動態調整 canvas 的大小,canvas 將被“重置”到一個新的初始狀態,即便是如下這種操作,也會將 canvas 內的位圖清除并將所有相關屬性恢復到初始值的狀態。當然,我們也可以把這當作重置 canvas 的小技巧來使用。
document.getElementById("canvas").width = document.getElementById("canvas").width;
?
簡單圖形繪制
基于 canvas 的繪圖并不是直接在 canvas 標記所創建的繪圖畫面上進行各種繪圖操作,而是依賴畫面所提供的
渲染上下文(Rendering Context)
, 所有的繪圖命令和屬性都定義在渲染上下文當中。在通過 canvas id 獲取相應的 DOM 對象之后首先要做的事情就是獲取渲染上下文對象。 渲染上下文與 canvas 一一對應,無論對同一 canvas 對象調用幾次 getContext() 方法,都將返回同一個上下文對象。目前,所有支持 canvas 標簽的瀏覽器都支持 2D 渲染上下文,可以使用如下的代碼來獲取該對象。
?
var context = document.getElementById("canvas").getContext("2d");
?
除此之外,在不久的將來,開發人員還會能夠得到基于 OpenGL 的 3D 渲染上下文以在 canvas 中進行 3D 繪圖。
與 SVG 不同,canvas 原生支持的基本圖形只有矩形一種,至于其他的圓形,多邊形等圖形則都由路徑來負責繪制實現。清單 1 展示了如何使用渲染上下文中的矩形繪圖方法完成了圖 1 所示圖形。
?
圖 1. 清單 1 對應的示例圖形
?
清單 1. 繪制 canvas 矩形
?
function drawRect(){ var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); // 獲取 2D 渲染上下文 ctx.clearRect(0,0,300,200) ;// 清除以(0,0)為左上坐標原點,300*200 矩形區域內所有像素 ctx.fillStyle = '#00f'; // 設置矩形的填充屬性,#00f 代表藍色 ctx.strokeStyle = '#f00'; // 設置矩形的線條顏色,#f00 代表紅色 ctx.fillRect(50,25,150,80); // 使用 fillStyle 填充一個 150*80 大小的矩形 ctx.strokeRect(45,20, 160, 90); // 以 strokeStype 屬性為邊的顏色繪制一個無填充矩形 } }
?
繪制路徑
在開始動手繪制路徑之前,首先需要明確的是:矩形繪制 API 是一種即時性的 API,他會在相應的繪圖函數執行完畢之后,將圖形即時的渲染在畫面上。然而路徑繪制 API 并非如此,完整的路徑繪制過程大致可以分為如下兩個階段:
?
- 定義路徑輪廓:
在每個 canvas 實例對象中都擁有一個 path 對象,創建自定義圖形的過程就是不斷對 path 對象操作的過程。每當開始一次新的圖形繪制任務,都需要先使用 beginPath() 方法來重置 path 對象至初始狀態,進而通過一系列對 moveTo/lineTo 等畫線方法的調用,繪制期望的路徑,其中 moveTo(x, y) 方法設置繪圖起始坐標,而 lineTo(x,y) 等畫線方法可以從當前起點繪制直線,圓弧以及曲線到目標位置。最后一步,也是可選的步驟,是調用 closePath() 方法將自定義圖形進行閉合,該方法將自動創建一條從當前坐標到起始坐標的直線。
?
- 繪制路徑
定義完路徑的輪廓,此時 canvas 畫面中沒有顯示任何路徑,開發人員還可以對路徑進行修改。一旦確定完成,則需要繼續調用 stroke()/fill() 函數來完成將路徑渲染到畫面的最后一步。路徑的輪廓顏色和填充顏色由 strokeStyle 和 fillStyle 屬性決定。
清單 2 繪制一個圖 2 所示半圓弧,并通過 closePath() 方法完成圖形的閉合。
?
圖 2. 清單 2 對應的示例圖形
?
清單 2. 繪制 canvas 路徑
?
function draw(){ var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); ctx.fillStyle = '#00f'; ctx.strokeStyle = '#f00'; ctx.beginPath(); ctx.arc(75,75,30,0,Math.PI, false); // 繪制一條半圓弧線 ctx.closePath(); // 自動繪制一條直線來關閉弧線。若不調用此方法,將僅僅顯示一條半圓弧 ctx.fill(); // 可以嘗試注釋掉 fill 或者 stroke 函數,觀察圖形的變化 ctx.stroke(); } }
?
二維變形
Canvas 繪圖中另一個重要的概念是 繪畫狀態(Drawing State) ,繪畫狀態反映了渲染上下文當前的瞬時狀態,開發人員可以通過對繪畫狀態的保存 / 恢復操作而快速的回到之前使用的各種屬性和變形操作。繪畫狀態主要由以下三個部分構成:
- 當前的變形矩陣(transformation matrix)
- 當前的裁剪區域(clipping region)
- 當前上下文中的屬性,比如 strokeStyle, fillType, globalAlpha, font 等等。
需要指出的是,當前路徑對象以及當前的位圖都不包含在繪畫狀態之中,路徑是持續性的對象,如前文所講,只有通過 beginPath() 操作才會進行重置,而位圖則是 canvas 的屬性,并非屬于渲染上下文的。
開發人員可以使用 save 和 restore 兩種方法來保存和恢復 canvas 狀態,每調用 save 方法,都會將當前狀態壓入堆棧中,而相應的 restore 方法則會從堆棧中彈出一個狀態,并將當前畫面恢復至該狀態。繪畫狀態在 canvas 圖形變形操作中應用極為廣泛,也非常重要,因為調用一個 restore 方法遠比手動恢復先前狀態要簡單許多,因而,一個較好的習慣是在做變形操作之前先保存 canvas 狀態。
二維繪圖的常用變形操作在 canvas 中都可到了很好的支持,包括平移(Translate),旋轉(Rotate),伸縮(Scale)等等。由于所有的變形操作都基于變形矩陣,因而開發人 員始終需要記住一點的就是,一旦沒有使用 save/restore 操作保持住原來的繪圖狀態,那么后續的繪圖操作,都會在當前所應用的變形狀態下完成。清單 3 使用平移和旋轉方法繪制了如下所示畫面。
?
圖 3. 清單 3 所示示例圖形
?
清單 3. 使用平移 / 旋轉變形方法繪制復雜位圖
?
function drawPointCircle(){ var canvas = document.getElementById('canvas'); if (canvas.getContext){ var ctx = canvas.getContext('2d'); ctx.translate(150,150); // 將 canvas 的原點從 (0,0) 平移至(150,150) for (i=1;i<=2;i++){ // 繪制內外 2 層 if ((i % 2) == 1) {ctx.fillStyle = '#00f';} else{ ctx.fillStyle = '#f00'; } ctx.save(); // 保持開始繪制每一層時的狀態一致 for (j=0;j<=i*6;j++){ // 每層生成點的數量 ctx.rotate(Math.PI/(3*i)); // 繞當前原點將坐標系順時針旋轉 Math.Pi/(3*i) 度 ctx.beginPath(); ctx.arc(0,20*i,5,0,Math.PI*2,true); ctx.fill(); // 使用 fillType 值填充每個點 } ctx.restore(); } } }
?
像素級繪圖
像素級別的繪圖操作是 canvas 繪圖區別于 SVG,VML 等繪圖技術的最為明顯特征之一,渲染上下文提供了 createImageData, getImageData, 和 putImageData 三種方法來進行針對像素的操作,所基于的對象都是 imageData 對象。imageData 對象包含 width、height 和 data 三個屬性,其中 data 包含了 width × height × 4 個像素值,之所以乘以 4,在于每個像素都有 RGB 值和透明度 alpha 值。
清單 4 中所示代碼為上一節中示例圖形增添了簡單的顏色反轉濾鏡效果,通過調用 getImageData(x,y,width,height) 方法獲取以(x,y)為左上坐標的矩形區域內所有像素,而后對所有像素的 RGB 值做取反操作,最后通過 putImageData(imageData, x, y)將修改后的像素值重新繪制到在 canvas 上。
?
圖 4. 清單 4 所示示例圖形
?
清單 4. 實現簡單濾鏡效果
?
function revertImage(){
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var context = canvas.getContext('2d');
// 從指定的矩形區域獲取 canvas 像素數組
var imgdata = context.getImageData(100, 100, 100, 100);
var pixels = imgdata.data;
// 遍歷每個像素并對 RGB 值進行取反
for (var i=0, n=pixels.length; i<n; i+= 4){
pixels[i] = 255-pixels[i];
pixels[i+1] = 255-pixels[i+1];
pixels[i+2] = 255-pixels[i+2];
}
// 在指定位置進行像素重繪
context.putImageData(imgdata, 100, 100);
}
}
?
實現動畫效果
Canvas 并非為了制作動畫而出現,自然沒有動畫制作中幀的概念。因而,使用定時器不斷的重繪 canvas 畫面成為了實現動畫效果的通用解決方式。Javascript 中的 setInterval (code,millisec ) 方法可以按照指定的時間間隔 millisec 來反復調用 code 所指向的函數或代碼串,這樣,通過將繪圖函數作為第一個參數傳給 setInterval 方法,在每次被調用的過程中移動畫面中圖形的位置,來最終達到一種動畫的體驗。需要注意的一點是,雖然 setinterval 方法的第二個參數允許開發人員對繪圖函數的調用頻率進行設定,但這始終都是一種最為理想的情況,由于這種繪圖頻率很大程度上取決于支持 canvas 的底層 JavaScript 引擎的渲染速度以及相應繪圖函數的復雜性,因而實際運行的結果往往都是要慢于指定繪圖頻率的。
清單 5 顯示了一個小彈力球動畫效果,在球沒有到達四周邊界時,繪圖方法不斷的移動所繪小球的橫縱坐標。并且,在每次重繪之前,都是用 clear 方法將之前的畫面清除。
?
清單 5. 實現小彈力球動畫
?
<script type="text/javascript"> var x=0,y=0,dx=2,dy=3,context2D; // 小球從(0,0)開始移動,橫向步長為 2,縱向步長為 3 function draw(){ context2D.clearRect(0, 0, canvas.width, canvas.height); // 清除整個 canvas 畫面 drawCircle(x, y); // 使用自定義的畫圓方法,在當前(x,y)坐標出畫一個圓 // 判斷邊界值,調整 dx/dy 以改變 x/y 坐標變化方向。 if (x + dx > canvas.width || x + dx < 0) dx = -dx; if (y + dy > canvas.height || y + dy < 0) dy = -dy; x += dx; y += dy; } window.onload = function (){ var canvas = document.getElementById('canvas'); context2D = canvas.getContext('2d'); setInterval(draw, 20); // 設置繪圖周期為 20 毫秒 } </script>
?
提高可訪問性
一款優秀的 Web 應用必須要做到的就是提供給用戶很好的可訪問性,這包括對鼠標,鍵盤以及快捷鍵等操作的響應,canvas 畫面的本質仍是一個 DOM 節點,因而開發人員可以通過常規的方法來處理響應。這里,與基于 SVG 的繪圖不同,由于 SVG 是一種基于 XML 的聲明式的繪圖方式,因而,SVG 中任何的圖形都可以作為一個獨立的 DOM 節點去接收并響應特定事件,而 canvas 由于其像素繪圖的本質,則只可以在 canvas 元素節點去處理。
?
圖 5 所示示例代碼,當鼠標在 canvas 中移動時,鼠標當前相對于 canvas 中的橫縱坐標將實時輸出到上方提示信息區域;當用戶在 canvas 中單擊鼠標左鍵,將在相應位置創建一個藍色小球,而后用戶可以通過鍵盤上的左 / 右方向鍵對藍色小球進行控制,使其進行橫向的移動。示例代碼如清單 6 所示。
?
圖 5. 清單 6 所示示例展現
?
清單 6. 實現 canvas 對方向鍵和鼠標點擊事件的響應
?
<script type="text/javascript"> var g_x,g_y; // 鼠標當前的坐標 var g_pointx, g_pointy; // 藍色小球當前的坐標 var canvas; function drawCircle(x,y){ // 以鼠標當前位置為原點繪制一個藍色小球 var ctx = canvas.getContext('2d'); ctx.clearRect(0,0,300,300); ctx.fillStyle = '#00f'; ctx.beginPath(); ctx.arc(x,y,20,0,Math.PI*2,true); ctx.fill(); g_pointx = x; g_pointy = y } function onMouseMove(evt) { // 獲取鼠標在 canvas 中的坐標位置 if (evt.layerX || evt.layerX == 0) { // FireFox g_x = evt.layerX; g_y = evt.layerY; } document.getElementById("xinfo").innerHTML = g_x; document.getElementById("yinfo").innerHTML = g_y; } function onKeyPress(evt) { var dx = 3; // 橫向平移步長 var kbinfo = document.getElementById("kbinfo"); if (evt.keyCode == 39){ kbinfo.innerHTML="right"; if (g_x<300-dx) drawCircle(g_pointx+dx,g_pointy); document.getElementById("xinfo").innerHTML = g_pointx; }else if (evt.keyCode == 37){ kbinfo.innerHTML = "left"; if (g_x>dx) drawCircle(g_pointx-dx,g_pointy); document.getElementById("xinfo").innerHTML = g_pointx; } } window.onload = function(){ canvas = document.getElementById('canvas'); // 增加 canvas 節點對鼠標單擊,移動以及鍵盤事件的響應函數 canvas.addEventListener('click', function(evt){drawCircle(g_x, g_y);} , false); canvas.addEventListener('mousemove', onMouseMove, false); canvas.addEventListener('keypress', onKeyPress, false); canvas.focus(); // 獲得焦點之后,才能夠對鍵盤事件進行捕獲 } </script>
?
這里我們對鼠標的移動,單擊操作進行響應,在實際應用中可以視特定應用的需求,增加對鼠標摁下,松開或雙擊等更為豐富操作的響應,增強應用的可訪問性。
細心的讀者可能發現,在通過不斷重繪畫面以達到動畫效果的過程中,我們的重繪方法首先做的事情都是調用 clearRect(x, y, width, height) 方法將原畫面清空,這種銷毀而后重繪的方式丟失了之前的畫面,使得開發人員不得不重繪整幅畫面,這在性能上是難以接受的,一種可行的做法是通過多個 canvas 疊加的方式,根據不同 canvas 上的不同刷新頻率,分別完成各自的重繪任務。這種多 canvas 技巧,在處理繪圖類應用中最為常見的“撤銷”操作時也非常有效,所有的繪圖都發生在上層 canvas,只有被用戶確認的畫面,才會被繪制到底層 canvas 上。鑒于本文所討論技術范圍,這里不做過多講解,有興趣的讀者可以通過本文參考文獻所列資源,進行進一步的深入學習。
?
總結
本文對 HTML5 新引入的 canvas 元素在 Web 繪圖中所扮演的角色和所發揮的作用做了最基本的介紹,其中包括使用 canvas 完成基本的 Web 繪圖,動畫和交互任務,雖然 Flash,Silverlight 也都可以完成相同的任務,甚至在性能上更勝一籌,但是作為一種不依賴任何插件的標準 Web 像素級繪圖技術,我們有理由相信隨著各大瀏覽器廠商的加入,canvas 將會更加成熟完善,也會有更多基于 canvas 的繪圖類應用不斷涌現。
?
聲明
本人所發表的內容僅為個人觀點,不代表 IBM 公司立場、戰略和觀點。
?
參考資料
學習
- 查看 HTML5 專題 ,了解更多和 HTML5 相關的知識和動向。
- 查看 Mozilla 開發者中心 ,了解更多 HTML5 canvas 知識。
- 查看 WhatWG 制定的關于 HTML5 canvas 規范 ,了解更多底層繪圖 API。
- 查看 Opera 開發者中心 ,了解如何使用 canvas 創建 Web 繪圖類應用。
- 通過 結合 GFX, DnD 與 Dijit 創建基于 Dojo 的 Web 圖形類應用 ,了解如何基于 SVG 和 Dojo 構建 Web 繪圖類應用。
- 訪問 這里 ,查看更多基于 HTML5 canvas 構建的精彩示例。
- developerWorks Web development專區 :通過專門關于 Web 技術的文章和教程,擴展您在網站開發方面的技能。
- developerWorks Ajax 資源中心 :這是有關 Ajax 編程模型信息的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新信息都能在這里找到。
- developerWorks Web 2.0 資源中心 ,這是有關 Web 2.0 相關信息的一站式中心,包括大量 Web 2.0 技術文章、教程、下載和相關技術資源。您還可以通過 Web 2.0 新手入門 欄目,迅速了解 Web 2.0 的相關概念。
討論
原文: http://www.ibm.com/developerworks/cn/web/1012_linlin_html5canvas/index.html
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
