canvas-应用大全
前言
去年在公司內部做了一次canvas的分享,或者說canvas總結會更為貼切,但由于一直都因為公事或者私事,一直沒有把東西總結成文章分享給大家,實在抱歉~ 分享這篇文章的目的是為了讓同學們對canvas有一個全面的認識,廢話不多說,開拔!
介紹
Canvas是一個可以使用腳本(通常為Javascript,其它比如?Java Applets or JavaFX/JavaFX Script)來繪制圖形,默認大小為300像素×150像素的HTML元素。
<canvas style="background: purple;"></canvas> 復制代碼小試牛刀
<!-- canvas --> <canvas id="canvas"></canvas> <!-- javascript --> <script>const canvas = document.getElementById('canvas')const ctx = canvas.getContext('2d')ctx.fillStyle = 'purple'ctx.fillRect(0, 0, 300, 150) </script> 復制代碼經過了以上地獄般的學習,我相信同學們現在已精通canvas。 接下來,我將介紹很多案例,把自己能想到的都列舉出來,并且,結合其原理,為同學們一一介紹。
應用案例
案例如下:
- 動畫
- 游戲
- 視頻(因為生產環境還不成熟,略)
- 截圖
- 合成圖
- 分享網頁截圖
- 濾鏡
- 摳圖
- 旋轉、縮放、位移、形變
- 粒子
動畫
API介紹
requestAnimationFrame
該方法告訴瀏覽器您希望執行動畫并請求瀏覽器在下一次重繪之前調用指定的函數來更新動畫。 該方法使用一個回調函數作為參數,這個回調函數會在瀏覽器重繪之前調用。
requestAnimationFrame 優點
1.避免掉幀 完全依賴瀏覽器的繪制頻率,從而避免過度繪制,影響電池壽命。 2.提升性能 當Tab或隱藏的iframe里,暫停調用。
Demo
方塊移動
<!-- canvas --> <canvas id="canvas" width="600" height="600"></canvas> <!-- javascript --> <script>const canvas = document.getElementById('canvas')const ctx = canvas.getContext('2d')ctx.fillStyle = 'purple'const step = 1 // 每步的長度let xPosition = 0 // x坐標move() // call movefunction move() {ctx.clearRect(0, 0, 600, 600)ctx.fillRect(xPosition, 0, 300, 150)xPosition += stepif (xPosition <= 300) {requestAnimationFrame(() => {move()})}} </script> 復制代碼游戲
三要素
個人做游戲總結的三要素:
- 對象抽象
- requestAnimationFrame
- 緩動函數
對象抽象:即對游戲中角色的抽象,面向對象的思維在游戲中非常地普遍。舉個例子,我們來抽象一個《勇者斗惡龍》里的史萊姆:
class Slime {constructor(hp, mp, level, attack, defence) {this.hp = hpthis.mp = mpthis.level = levelthis.attack = attackthis.defence = defence}bite() {return this.attack}fire() {return this.attack * 2} } 復制代碼requestAnimationFrame:之前我們已經接觸過這個API了,結合上面動畫的例子,我們很容易自然的就能想到,游戲動起來的原理了。
緩動函數:我們知道,勻速運動的動畫會顯得非常不自然,要變得自然就得時而加速,時而減速,那樣動畫就會變得更加靈活,不再生硬。
Demo
有興趣的同學可以看我以前寫的小游戲。 項目地址:github.com/CodeLittleP…
截圖
API介紹
drawImage(image, sx, sy [, sWidth, sHeight [, dx, dy, dWidth, dHeight]])
繪制圖像方法。
toDataURL(type, encoderOptions)
方法返回一個包含圖片展示的 data URI 。可以使用 type 參數其類型,默認為 PNG 格式。圖片的分辨率為96dpi。 注意:
- 該方法必須在http服務下
- 非同源的圖片需要CORS支持,圖片設置crossOrigin =“”(只要crossOrigin的屬性值不是use-credentials,全部都會解析為anonymous,包括空字符串,包括類似'abc'這樣的字符)
canvas.style.width 和 canvas.width 的區別
把canvas元素比作畫框: canvas.width則是控制畫框尺寸的方式。 canvas.style.width則是控制在畫框中的畫尺寸的方式。Demo
核心代碼
const captureResultBox = document.getElementById('captureResultBox') const captureRect = document.getElementById('captureRect') const style = window.getComputedStyle(captureRect) // 設置canvas畫布大小 canvas.width = parseInt(style.width) canvas.height = parseInt(style.height) // 畫圖 const x = parseInt(style.left) const y = parseInt(style.top) const w = parseInt(img.width) const h = parseInt(img.height) ctx.drawImage(img, x, y, w, h, 0, 0, w, h) // 將圖片append到html中 const resultImg = document.createElement('img') // toDataURL必須在http服務中 resultImg.src = canvas.toDataURL('image/png', 0.92) 復制代碼合成圖
原理
回看之前的例子,我們知道了drawImage可以自己畫圖畫,也可以畫圖片。canvas完全就是個畫板,可任由我們發揮。 合成的思路其實就是把多張圖片都畫在同一個畫布(cavans)里。是不是一下子就知道接下來怎么做啦?
Demo
核心代碼
// 設置畫布大小canvas.width = bg.widthcanvas.height = bg.height// 畫背景ctx.drawImage(bg, 0, 0)// 畫第一個角色ctx.drawImage(character1, 100, 200,character1.width / 2,character1.height / 2)// 畫第二個角色ctx.drawImage(character2, 500, 200,character2.width / 2,character2.height / 2) 復制代碼如圖,背景是一深夜無人后院,然后去網上搜兩張背景透明的角色圖片,再將兩張圖一次畫到畫布上就成了合成圖啦。
分享網頁截圖
原理
拿比較出名的html2canvas為例,實現方式就是遍歷整個dom,然后挨個拉取樣式,在canvas上一個個地畫出來。
Demo
濾鏡
API介紹
getImageData(sx, sy, sw, sh)
返回一個ImageData對象,用來描述canvas區域隱含的像素數據,這個區域通過矩形表示,起始點為(sx, sy)、寬為sw、高為sh。 看段代碼:
const img = document.createElement('img') img.src = './filter.jpg' img.addEventListener('load', () => {canvas.width = img.widthcanvas.height = img.heightctx.drawImage(img, 0, 0)console.log(ctx.getImageData(0, 0, canvas.width, canvas.height)) }) 復制代碼它會打印出如下數據:
有點迷?不慌,接下去看。
數據類型介紹
Uint8ClampedArray
8位無符號整型固定數組) 類型化數組表示一個由值固定在0-255區間的8位無符號整型組成的數組;如果你指定一個在 [0,255] 區間外的值,它將被替換為0或255;如果你指定一個非整數,那么它將被設置為最接近它的整數。(數組)內容被初始化為0。一旦(數組)被創建,你可以使用對象的方法引用數組里的元素,或使用標準的數組索引語法(即使用方括號標記)。 回看這張圖:
data里其實就是像素,按每4個為一組成為一個像素。 4個一組,難道是rgba? (o゜▽゜)o☆[BINGO!] 這樣的話,圖片的寬x高x4(w * h * 4 )就是所有像素的總和,剛好就死data的length。數學推導
已知:924160 = 640 x 316 x 4 可知:數組的長度為length = canvas.width x canvas.height x 4
知道了這種關系,我們不妨把這個一維數組想象成二維數組,想象它是一個平面圖,如圖:
一個格子代表一個像素
w = 圖像寬度
h = 圖像高度
這樣,我們可以很容易得到點(x, y)在一維數組中對應的位置。我們想一想,點(1, 1)坐標對應的是數組下標為0,點(2, 1)對應的是數組下標4,假設圖像寬度為2*2,那么點(1,2)對應下標就是index=((2 - 1)*w + (1 - 1))*4 = 8。
推導出公式:index = [(y - 1) * w + (x - 1) ] * 4
繼續API介紹
createImageData(width, height)
createImageData是在canvas在取渲染上下文為2D(即canvas.getContext(‘2d'))的時候提供的接口。作用是創建一個新的、空的、特定尺寸的ImageData對象。其中所有的像素點初始都為黑色透明。并返回該ImageData對象。
putImageData
putImageData方法作為canvas 2D API 以給定的ImageData對象繪制數據進位圖。如果提供了臟矩形,將只有矩形的像素會被繪制。這個方法不會影響canvas的形變矩陣。
這小節我們學了好幾個新API,然后重新理了理數學知識。同學們好好消化完以后,就進Demo階段吧。
Demo
核心代碼:
最終效果:摳圖
對于純背景摳圖,其實還是比較簡單的。上面我們已經說過,我們可以拿到整個canvas的每個像素點的值了。所以,只需要把純色的色值轉為透明就好了。 但這種場景不多,因為,背景很少有純色的情況,而且即使背景純色,不保證被扣對象的身上沒有和背景同色值的情況。 所以,如果要處理復雜的情況,還是建議后端來做比較好,后端早已有了成熟的圖像處理解決方案,比如opencv等。像美圖的話,有專門的圖像算法團隊,天天研究這方面。 接下來,我將介紹下美圖人像摳圖的思路。
屬性介紹
globalCompositeOperation
控制drawImage的繪制圖層先后順序。
思路
我們將使用souce-in這個屬性。如上圖所示,這個屬性的作用是,兩圖疊加,只取疊加的部分。 為什么這樣搞?不是說好了,美圖是讓后端算法大佬們處理嗎? 因為,為了人像摳圖適應更多的場景,算法大佬們只會把人物圖像處理成一個蒙版圖并返給前端,之后讓前端自己處理。 我們看下原圖:
再看下后端返給的蒙版圖:
得到以上的蒙版圖以后,先把黑色處理成透明; 先在canvas上draw原圖; 再把globalCompositeOperation 設置為 'source-in'; 然后再draw處理后的蒙版圖; 得到的就是最后的摳圖啦! 這個方案是咨詢前美圖大佬@xd-tayde的,感謝~
Demo
處理結果:
旋轉、縮放、位移、形變
對于旋轉、縮放、位移、形變,canvas的上下文ctx有對應的API可以調用,也可以用martrix方式做更高級的變化。因為涉及的內容很多,如果全寫這的話,篇幅太大。 所以,我這里直接推薦一篇文章給同學們學習 ——《canvas 圖像旋轉與翻轉姿勢解鎖》
粒子
抽象
之前我們就知道了,我們可以獲取canvas上的每個像素點。 所謂的粒子,其實算是對一個像素的抽象。它具有自己坐標,自己的色值,可以通過改變自身的屬性“動”起來。 因此我們不妨將粒子作為一個對象來看待,它有坐標和色值,如:
let particle = {x: 0,y: 0,rgba: '(1, 1, 1, 1)' } 復制代碼Demo - 小試牛刀
我將把一張網易支付的logo圖,用散落的粒子重新畫出來。 核心代碼:
// 獲取像素顏色信息const originImageData = ctx.getImageData(0, 0, canvas.width, canvas.height)const originImageDataValue = originImageData.dataconst w = canvas.widthconst h = canvas.heightlet colors = []let index = 0for (let y = 1; y <= h; y++) {for (let x = 1; x <= w ; x++) {const r = originImageDataValue[index]const g = originImageDataValue[index + 1]const b = originImageDataValue[index + 2]const a = originImageDataValue[index + 3]index += 4// 將像素位置打亂,保存進返回數據中colors.push({x: x + getRandomArbitrary(-OFFSET, OFFSET),y: y + getRandomArbitrary(-OFFSET, OFFSET),color: `rgba(${r}, ${g}, ${b}, ${a})`})} 復制代碼效果:
Demo - 粒子動畫
三要素
- 粒子對象化
- 緩動函數
- 性能
粒子對象化已經介紹過了。 緩動函數,在之前的游戲也提及過,是為了讓動畫更加的自然生動。 性能是一個很需要關注的問題。因為比如一張500x500的圖片,那數據量就是500x500x4=1000000。動畫借助了requestAnimationFrame,正常的情況下一般刷新頻率在60HZ,能展現非常流暢的動畫。但現在要處理這么大的數據量,瀏覽器抗不過來了,自然造成了降頻,導致動畫卡幀嚴重。
為了性能,粒子動畫往往采用選擇性的選取像素用來繪制。比如,只繪制原圖x坐標為偶數,或能被4等整除的像素。比如,只繪制原圖對應像素r色值為155以上的像素。
結合上面的思路,就可以做出各種強大的例子動畫啦。
Demo
所有Demo項目地址
github.com/CodeLittleP…
原文出處
《canvas-應用大全》
參考文章
《打造高大上的 Canvas 粒子動畫 - 騰訊 ISUX》
轉載于:https://juejin.im/post/5c81e6de5188257ade0231f4
總結
以上是生活随笔為你收集整理的canvas-应用大全的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 设计模式(四)OkHttp的责任链模式
- 下一篇: 学习 Spring (十三) AOP 配