作者本人的乾貨,一般人我不給他看。(開玩笑的)
canvas是一個很實用的工具,難可以難到頭髮掉光,簡單可以簡單到幾行程式碼就能給人眼前一亮的效果。這裡是作者在開發canvas的道路上遇過的坑,以及如何用簡易地使用canvas做一些日常任務,比如分享圖片的自定義,又比如大家喜歡的X炸天的粒子特效(不知道算不算,反正很COOL就是了)。
No.1 Canvas的正確開啟方式
大家都知道Canvas可以做流暢的動畫,功能很強大,但是Canvas中並沒有像Dom那樣可以幫助我們除錯的工具。不過現在mac有一款web inspector(webkit.org/downloads//)可以除錯canvas,有興趣的同學可以去試一下。
不過這裡還有一種方式可以幫助大家除錯Canvas。Canvas其實就是一塊繪板,隨便你在上面畫什麼。但是又不像PS那樣有輔助線,因此定位很艱辛。
建立網格
- mark api:
- context.fillStyle
- context.textAlign
- context.font
- context.textBaseline
- context.fillRect
- context.fillText
因此,手動給Canvas加上網格,不就可以了!這裡我們可以建立一個繪製網格的方法,然後每次render的時候呼叫,這樣就可以對圖形的定位有一個直觀的感受了。再也不用抓瞎。
首先我們要計算好網格的數量,將所有計算好的網線放入一個陣列中。雖然我們也可以動態計算,網格的位置,但是從效能上考慮,canvas中凡是在繪圖之前可以確認的位置都提前計算好,這樣可以提高效能。這裡我留了一點空間給座標值,因此並不是全屏的網格。
let Grids=[]
function initGrid(cap,width,height,lineWidth){
const colNum=Math.ceil(width/cap)-1
const rowNum=Math.ceil(height/cap)-1
for(let i=1;i<=colNum;i++){
Grids.push([[cap*i-1, 0,lineWidth,colNum*cap],[i*cap,cap*i-1,colNum*cap+5,"top"]])
}
for(let i=1;i<=rowNum;i++){
Grids.push([[ 0,cap*i-1,rowNum*cap,lineWidth],[i*cap,rowNum*cap+5,cap*i-1,"middle"]])
}
}
initGrid(cap,canvasWidth,canvasHeight,lineWidth);
複製程式碼
計算並儲存好資訊之後,我們就要開始畫線了。對於線的寬度最好是雙數,然後位置偏1px,然線居中,因為.5px在有些機子上效能不好,比如四捨五入一類的,容易有偏差。
function createGrid(){
context.fillStyle = 'green';
context.textAlign="center"
context.font="24px Arial"
Grids.forEach((grid)=>{
context.textBaseline=grid[1][3]
context.fillRect( grid[0][0],grid[0][1], grid[0][2], grid[0][3])
context.fillText (grid[1][0],grid[1][1], grid[1][2]);
})
}
複製程式碼
畫一個幾何圖形
- mark api:
- context.beginPath();
- context.moveTo (50, 50);
- context.lineTo (450, 450);
- context.closePath()
注意這裡beginPath是一切新開始的意思,也就是當前圖和上一個操作已經沒有了任何關係。closePath就是結束的意思,一切回到最初開始的地方,就是最先開始的那個點。lineTo就是畫線的意思,兩個點之間畫一條直線。我們可以搭配moveTo使用,moveTo就是移動到當前點,但是並不繪製任何內容。
如果我們只是繪製圖形,並無其他操作,比如每段路徑的顏色不一樣或者是填充顏色不一樣,那麼moveTo和beiginPath的作用差不多,但是如果是,那麼每次都需要beginPath一下,切斷與上一個路徑的聯絡,否則會以最後的設定的填充色路徑色為主。
那麼問題來了我直接closePath可以嗎?當然不行,你可以說開始就開始,但不能說結束就結束!closePath最大的作用就是連線路徑最後一個點和路徑最開始的點。
橡皮擦
因為Canvas是畫布,所以每次畫面更新時都是擦乾淨,再畫下一幅畫,不然就會重疊。大家想想一下幀動畫,就是1s中N幅畫劃過的動態感,變成了會動的動畫。如果是jpg這種不透明的圖片還可以一層層覆蓋,如果是png透明的圖片,一層層就會堆疊在一起。所以橡皮擦的功能時必不可少的。
- mark api:
- context.clearRect (x,y,width,height);
其實就是畫一個矩形,矩形所在的地方影象消失。
線上玩耍地址:
See the Pen canvas No.1 by cherryvenus (@cherryvenus) on CodePen.
No.2 Canvas的實用工具
Canvas中有幾個小知識點,非常的實用,而且應該是日常開發中基本上都要使用的。
Canvas的畫素點
首先就是畫素的問題,大家有沒有遇到過Canvas模糊的問題,尤其是手機,這個現象尤為明顯。那麼有沒有解決方案呢?答案是當然有!而且並不複雜,一個屬性就可以搞定!
Canvas的尺寸其實又兩個,不知道大家有沒有發現。一個時Canvas的大小,一個是Canvas的樣式大小。
canvas.width=cWidth
canvas.height=cHeight
canvas.style.width=cWidth
canvas.style.height=cHeight
複製程式碼
那麼這就好解決的。我們的圖片如果1:1放在手機上肯定是模的,但是我們會將圖片的樣式寬度減少一半以上,這樣就不模糊了!Canvas也是同理,只要樣式大小小於Canvas大小即可。那麼小多少呢?有沒有一個標準?這個時候就要借用window.devicePixelRatio
這個引數了,告訴我們螢幕的畫素比,如果沒有就2,一般來說畫素比是2的情況下,Canvas就不會模糊。
const dpr=window.devicePixelRatio||2
canvas.width=cWidth*dpr
canvas.height=cHeight*dpr
canvas.style.width=cWidth
canvas.style.height=cHeight
複製程式碼
論如何儲存Canvas的影象
我們的H5在哪裡的傳播最多?微信啊!我們經常接到一個功能,讓使用者儲存圖片,分享到朋友圈。通常這個圖片是使用者自己填寫內容,然後列印到螢幕上。最後合成,儲存的。那麼Canvas該如何幫助我們儲存圖片呢?
Canvas雖然不能直接儲存圖片,但是卻可以生成Base64的檔案。我們將Base64放入img標籤中,使用者就可以自由儲存了。
程式碼也很簡單canvas.toDataURL('image/jpeg');
。
程式碼玩耍地址:
See the Pen Canvas No.2 by cherryvenus (@cherryvenus) on CodePen.
No.3 save&restore,Canvas的回退功能
Canvas的程式碼一個不小心就會寫好多好多,每次新建一個操作都要寫好多內容。這個時候就要看看Canvas的複用操作了,一個是減少操作,還有一個就是減少機器的壓力,防止計算量過多。
談到save&restore,可能大家會有點懵,不知道這個是用來做什麼的。也不知道有什麼用。我們假想所有的canvas的配置,如fillSytle,strokeStyle的狀態都封裝在一個物件之中,然後每次save這個物件,就將這個物件push到一個Cavans狀態的陣列之中,之後我們可能改變了其中的一些屬性,然後我們又需要之前的哪個配置,怎麼辦?再寫一遍屬性配置嗎?不,這個時候我們可以用restore,一鍵切換至上一個狀態。也就是當前的配置全部失效。所有屬性值回退到之前的一個狀態。我們可以一直restore到預設值,也就是Canvas狀態陣列空了為止。
程式碼玩耍地址:
See the Pen Canvas No3 save&restore by cherryvenus (@cherryvenus) on CodePen.
No.4 最常用的drawImage全方位解讀
解析圖:
個人覺得Canvas中最頭疼的就是圖片的繪製了,drawImage這個一個方法,就可以幫助我們完成拉伸,剪下,放大,縮小的功能。
drawImage的總引數有9個,但是平時我們可以簡寫。
第一個引數一定是image,也就是我們的圖片物件。其餘的幾個引數,容易搞混。我們來詳細地看下。
引數 | 作用 |
---|---|
dx,dy | 這個最好理解,這裡是指圖片開始繪製的位置,如果設定這兩個引數,就是從這個(dx,dy)點開始繪製原始完整的圖片 |
dx,dy,dwidth,dheight | 這裡除了開始點(dx,dy),還有圖片在畫布上呈現的大小,這邊需要注意,雖然會畫完整的圖片,但是會按照dwidth和dheight的尺寸來,因此就會產生圖片變形的情況。 |
sx,sy,swidth,sheight,dx,dy,dwidth,dheight | 這個比較難以理解,前四個是對原始圖片的操作,也就獲取原始圖片的區域,後四個引數就是圖片需要繪製在畫布上的位置和大小。也就是圖片的所選區域放入畫布的所選區域。 |
玩耍地址:
See the Pen Canvas N0.4 by cherryvenus (@cherryvenus) on CodePen.
No5. X炸天的特效
- mark api:
- context.getImageData 獲取影象資訊
這個api是最amazing的方法,因為他幫助我們獲取了畫布的顏色資訊,通過這個資訊,我們可以重新創造新的圖片。這個方法除了體積大,沒啥毛病。因為他的data長度是按照width(寬)*height(高)*4(rgba四個顏色資訊)
組成的。
let info=context.getImageData(x,y,width,height)
//返回
data
width
height
複製程式碼
這裡要避免遍歷data(data.forEach
),來處理圖片資訊,因為很大,瀏覽器容易卡頓。最好按照定位資訊,獲取當前座標的顏色資訊。
至於最開始的那個特效,我是藉助了matter.js這個庫,才能完成的。如果是手寫特效的話,不如這個庫來的生動有趣。
play地址:
See the Pen canvas No5. COOL by cherryvenus (@cherryvenus) on CodePen.