過個年一下荒廢了個把月。 最近剛接觸canvas,將一些概念點簡單概括下,canvas是基於像素的圖像API,與svg的最大的區別在於canvas須要重繪(canvas移除圖片時須要從新繪製,而SVG能夠經過編輯元素節點來編輯圖片),而且基於基於像素繪製(svg顧名思義是矢量),更詳細的對比mark在此:?SVG 與 HTML5 的 canvas 各有什麼優勢 並且我我的認爲雖然canvas的API也很複雜,可是svg更復雜,囧rz。如下是我將我接觸canvas過程當中認爲須要釐清的概念點概括以下。html
canvas元素自己沒有任何外觀,它就是一塊空白畫板,提供給JS的一套API,最先由Safari引入,IE9以前可使用一些類庫在IE中模擬canvas,大部分的API都不在canvas元素自身定義,canvas元素自身屬性與常規的HTML元素並無多大區別, 它的繪圖API都定義在一個CanvasRenderingContext2D
對象上(這裏簡單翻譯成上下文對象),該對象經過getContext()
方法得到,代碼示例:canvas
<html> <head> <title>座標系demo</title> </head> <body> <canvas id = 'square' width= 200 heigth=200></canvas> </body> <script> var canvas = document.getElementById('square') var ctx = canvas.getContext('2d')//2d表示畫板維度,輸入3d將獲得一個更爲複雜的3d圖形API,也稱WebGL
圖像繪製須要參考座標系定位, canvas的默認座標系即畫布左上角原點(0,0),可是若是圖像的每次繪製都參考一個固定點將缺乏靈活性,因而canvas引入了「當前座標系」的概念,所謂「當前座標系」即指圖像在此時繪製的時候所參考的座標系,它也會做爲圖像狀態(圖像狀態的概念將在後介紹)的一部分。好比rotate
旋轉操做,改變當前座標系也就是改變了rotate
的參考點,試想下若是沒有當前座標系的概念,不管是旋轉,縮放,傾斜等操做不就只能參考畫布左上角原點了嗎(默認座標系)?iphone
canvas提供了translate()
和setTransform()
這兩個方法分別影響當前座標系與默認座標系。svg
translate()
與setTransform()
方法translate()
方法將座標原點進行上下左右移動。它所影響的就是在圖像在繪製的時候所參考的「當前座標系」,舉個例子?:spa
能夠直接在demo中操做觀察:.net
座標系DEMO翻譯
代碼:3d
<html> <head> <title>座標系demo</title> </head> <body> <canvas id = 'square' width= 200 heigth=200></canvas> </body> <script> var canvas = document.getElementById('square') var ctx = canvas.getContext('2d') ctx.beginPath() ctx.translate(20,20) //translate影響了當前座標系 ctx.moveTo(0,0) ctx.lineTo(100,20) ctx.stroke() </script> </html>
無任何座標系變化的圖像繪製:rest
translate()
方法將座標原定移動到(20,20)後獲得當前座標系後的繪製code
瞭解這點後setTransform()
也很容易,該方法影響的是默認座標系,也就是說它並不是將原點移來移去,而是重置當前座標系,定義一個新的默認座標系,什麼叫影響默認座標系,好比說前面的translate()
所移動的座標原點(0,0)仍是初始的默認座標系,而如今setTransform()
所影響的就是這個原點(0,0)的座標系,仍是以前的demo,當加入ctx.setTransform(1,0.5,-0.5,1,30,10)
這條語句後,圖像繪製將變成:
這是由於setTransform()
將默認座標系從新定義了,因而translate()
基於新的默認座標系來獲得當前座標系。理解了這兩個概念也就掌握了canvas中座標系的變換。
setTransform()
與transform()
方法setTransform()
這個API略複雜, 它所接受的參數與transform()
(使用transform()
可直接獲得一個變換結構,可代替rotate()
等方法,而且更爲靈活)同樣爲6個參數,setTransform(a,b,c,d,e,f)
而座標系變化的原理就是經過與這6個參數進行如下運算後得出的:
x' = ax + cy +e
y' = bx + dy +f
這種座標系變換也被稱爲仿射變換(affine transform),關於該變換的栗子可參考這兩篇博客:
?Html5 Canvas 變換矩陣與座標變形之間的關係
路徑是繪製全部圖形的基礎,不一樣於SVG中path
使用屬性M
,L
,A
等控制的XML文檔,canvas調用上下文對象的方法來完成路徑的繪製,調用beginPath()
開始一段新路徑,每段路經又有子路徑,正是依靠這些子路徑使得圖造成形。調用beginPath()
後調用MoveTo()
開始一段子路徑。繪製完成後使用closePath()
閉合路徑,從而造成一個閉合區域,這時候就可使用fill()
等方法填充該區域了。每次開始一段新路徑的繪製必須再次調用beginPath()
,不然新繪製的路徑將做爲以前路徑的子路徑繼續繪製。
相似於lineTo()
是最簡單的直線段路徑方法, canvas還提供了bezierCurveTo()
和quadraticCurveTo()
這些複雜的曲線路徑方法,很是複雜,因此估計通常這種操做仍是先找輪子解決。
另外須要注意的是,當一條路徑的兩條子路徑不相交的時候(好比繪製一個鏤空的圖形),畫布將採用「非零繞數原則」判斷某點是在路徑內仍是路徑外, 這樣以便於填充的時候區別哪些區域是須要填充的。
有關非零繞數原則的原理能夠參考這裏:mark? 非零環繞數規則和奇-偶規則
canvas的屬性與方法與咱們面向對象中的屬性方法並無太大區別,只是這裏涉及到了一個圖像狀態的概念。在canvas中,沒法經過getContext()
方法得到多個上下文(context)對象,而圖像屬性都是基於canvas的上下文對象,也就是說沒法同時擁有兩個屬性。形象地比喻就是圖像屬性就像畫筆, 粗細,大小,顏色。因爲同一時間只能有一個上下文對象因此只能同一時間使用一支畫筆。這時候當須要其它的圖像屬性(另外一支畫筆)的時候就只能經過保存當前圖像狀態,而後新建一個圖像狀態來切換。
這時候就須要藉助save()
和restore()
來切換圖像狀態,每次save()
都將保存當前圖像狀態,圖像狀態包括當前的圖像屬性,當前座標系,裁剪區域等信息。好比如下demo以兩種顏色畫線:
直接在demo中修改代碼觀察圖像狀態demo
JS代碼:
var canvas = document.getElementById('square') var ctx = canvas.getContext('2d') ctx.beginPath() ctx.strokeStyle = "red" ctx.moveTo(0,0) ctx.lineTo(100,20) ctx.stroke() ctx.save()//保存當前圖像狀態(畫筆) ctx.beginPath() ctx.strokeStyle = "blue" ctx.moveTo(0,0) ctx.lineTo(100,40) ctx.stroke() ctx.restore()//恢復到最近保存圖像狀態(畫筆) ctx.beginPath() ctx.moveTo(0,0) ctx.lineTo(100,60) ctx.stroke()
輸出以下:
這些圖像屬性包括:
fillStyle
font
globalAlpha
globalCompositeOperation
lineCap
lineJoin
lineWidth
miterLimit
textAlign
textBaseline
shadowBlur
shadowColor
shadowOffsetX
shadowOffsetY
strokeStyle
通常的純色背景填充可使用fillStyle
屬性,可是當涉及更復雜的圖片或者漸變色填充就須要CanvasPattern
和CanvasGradient
對象了,能夠經過creatPattern()
方法獲得CanvasPattern
,這裏須要注意的是該API不只能夠代入通常的圖片,也可使用canvas元素,好比畫面外一個不可見的canvas元素用於插入。
關於這兩個API的細節直接參考文檔:
基於像素的canvas能夠實現針對單個像素的操做,這也是畫布底層的API,經過調用getImageData()
方法將返回一個ImageData
對象,該對象表示畫布中原始的RGBA像素信息,經過調用creatImageData()
方法也能夠建立一個空的ImageData
對象,最後putImageData()
方法將處理後的像素輸出到畫布中。
微軟有篇不錯的教程(使用 Canvas 將彩色照片變成黑白照片)解釋像素操做,其中的操做是將彩色照片轉成灰白,使用的原理是將RGB三個份量提取出來,通過計算後(關鍵計算語句以下)從新賦值爲灰度變量。
myGray = parseInt((myRed + myGreen + myBlue) / 3); // Assign average to red, green, and blue. myImage.data[i] = myGray; myImage.data[i + 1] = myGray; myImage.data[i + 2] = myGray;