前文
相信接觸過一些canvas的小伙們都應(yīng)該會(huì)有這樣的一句感嘆: canvas 強(qiáng) 真的強(qiáng)!
不僅可以靜態(tài)的創(chuàng)建一些我們用普通標(biāo)簽無(wú)法實(shí)現(xiàn)的圖形,而且還能讓這些圖形動(dòng)起來(lái).
其實(shí)在實(shí)際的開發(fā)中,要是你只會(huì)用canvas畫一些矩形啊,三角形啊,五角星等等的東西肯定是不夠的.
因?yàn)檎嬲陂_發(fā)中,canvas大部分都是用來(lái)對(duì)圖片以及視頻做處理,所以博主今天在這里想要介紹的是一些關(guān)于canvas對(duì)圖片的處理
1. 引用圖片
我們知道想在網(wǎng)頁(yè)中顯示一張圖片,我們只需要用<img src="">
就可以實(shí)現(xiàn)了,那么在canvas中我們是怎樣插入一張圖片的呢.
1.首先在body中創(chuàng)建好一個(gè)canvas標(biāo)簽
<body>
<canvas id="canvas" width="500" height="500"></canvas>
</body>
2.在js代碼中獲取canvas并創(chuàng)建一個(gè)<img>元素
<script>
let canvas = document.querySelector('#canvas') //獲取canvas對(duì)象
let ctx = canvas.getContext('2d') //獲取2d上下文
let img = new Image() //創(chuàng)建img
img.src = 'img/green.jpg' //給img添加資源
</script>
3.? 繪制img,考慮到圖片是從網(wǎng)絡(luò)加載贝咙,如果 drawImage 的時(shí)候圖片還沒有完全加載完成,則什么都不做抱怔,個(gè)別瀏覽器會(huì)拋異常硝岗。所以我們應(yīng)該保證在 img 繪制完成之后再 drawImage
<script>
let canvas = document.querySelector('#canvas')
let ctx = canvas.getContext('2d')
let img = new Image()
img.src = 'img/green.jpg'
//圖片是否已經(jīng)加載完成
img.onload = function () {
ctx.drawImage(this, 100, 100, this.width / 2, this.height / 2)
}
</script>
通過上面的三個(gè)步驟,這時(shí)候打開你們的瀏覽器就可以在頁(yè)面中看到對(duì)應(yīng)的圖片了.
這里主要用到的是drawImage
這個(gè)方法,下面是對(duì)其的一些詳細(xì)講解.
2.解析drawImage( )
對(duì)于drawImage()這個(gè)方法,有三種使用的方式:
第一種:
只傳入3個(gè)參數(shù)
drawImage(image,x,y)
參數(shù)1: image:
指的就是你的圖片對(duì)象,
也就是你let img = new Image()
中的img
參數(shù)2 : x
圖片相對(duì)于畫布原點(diǎn)(0,0)也就是畫布的最左上角 的x軸方向的坐標(biāo)
參數(shù)3: y
圖片相對(duì)于畫布原點(diǎn)(0,0)也就是畫布的最左上角 的y軸方向的坐標(biāo)
第二種:
傳入5個(gè)參數(shù)
drawImage(image,x,y,width,height)
前面三個(gè)參數(shù)和第一種的使用方式一樣.
參數(shù)4,5: width 和 height
可以規(guī)定圖片的寬度和高度.
如:在畫布(100,100)的位置插入一張300*300的圖片
img.onload = function () {
ctx.drawImage(this, 100, 100, 300, 300)
}
那么利用width和height我們可以發(fā)現(xiàn),想要將圖片縮減為其原始大小的一半,就可以這樣寫:
img.onload = function () {
ctx.drawImage(this,100,100,this.width / 2, this.height / 2)
}
第三種:
傳入9個(gè)參數(shù)
當(dāng)在drawImage()中傳入9個(gè)參數(shù)后,這個(gè)方法的用法將和前面?zhèn)z種不一樣了.
它的用法是從圖片中截取一定尺寸的圖片,并
drawImage(image,sourceX,sourceY,sourceWidth,sourceHeight,x,y,width,height)
第三種的使用方式傳遞的是9個(gè)參數(shù),
參數(shù)1 : image
還是圖片的對(duì)象
參數(shù)2,3 : 從一張大圖上指定要截取小圖的位置(x,y)坐標(biāo)
參數(shù)4,5: 從一張大圖上指定要截取小圖的大小
參數(shù)6,7: 從一張大圖上截取下來(lái)的小圖要放在canvas(畫布)中的位置(x,y)
參數(shù)8,9: 截取下來(lái)小圖規(guī)定的寬高
如下圖中,有5架小飛機(jī),我只想截取最后一架并顯示在畫布中.
![herofly.png
整張圖的寬度是330px,一架飛機(jī)就是66px,所以最后一張圖就是從66 * 3 = 198px的位置開始截取,截取完后放在畫布(0, 0)的位置
var img1 = new Image()
img1.src = 'img/herofly.png'
drawImage(img1, 198, 0, 66, 82, 0, 0, 66, 82)
3.canvas中的動(dòng)畫
3.1 requestAnimationFrame的簡(jiǎn)介
我們利用普通的定時(shí)器來(lái)實(shí)現(xiàn)動(dòng)畫的寫法為:
var x = 0;
function animate(){
//清除畫布內(nèi)容
ctx.clearRect(0, 0, canvas.width, canvas.height);
x += 2;
ctx.fillStyle = "red";
ctx.fillRect(x, 0, 50, 50);
if (x > 200){
return;
}
setTimeout(animate,30);
}
animate();
可以看到上面的動(dòng)畫是靠setTimeout這個(gè)定時(shí)器每隔30毫秒調(diào)用一次animate() 來(lái)實(shí)現(xiàn)的.
這種利用定時(shí)器來(lái)實(shí)現(xiàn)動(dòng)畫效果在移動(dòng)端實(shí)際來(lái)說是很不可取的,在移動(dòng)端上看到的動(dòng)畫會(huì)很卡頓,造成用戶體驗(yàn)很不流程.
所以ES6新增了一個(gè)類似于定時(shí)器的API:
requestAnimationFrame()
它只有一個(gè)參數(shù),就是要執(zhí)行的函數(shù).
使用requestAnimationFrame實(shí)現(xiàn)動(dòng)畫
var x = 0;
function animate(){
//清除畫布內(nèi)容
ctx.clearRect(0, 0, canvas.width, canvas.height);
x += 2;
ctx.fillStyle = "red";
ctx.fillRect(x, 0, 50, 50);
if (x > 200){
return;
}
requestAnimationFrame(animate); //唯一不同
}
animate();
可以看到倆段代碼的區(qū)別,僅僅是一個(gè)用的是setTimout,一個(gè)是requestAnimationFrame
setTimout表示的是: 每隔30毫秒,執(zhí)行一次animate()函數(shù).
而requestAnimationFrame 在一秒中執(zhí)行多少次是由它的應(yīng)用場(chǎng)景決定的,一般都能達(dá)到58~60次.也就是1000/60(相當(dāng)于定時(shí)器16毫秒執(zhí)行一次)
那么這里得到的1000/60就是一幀.不同的場(chǎng)景幀數(shù)可能會(huì)不一樣.
3.2 canvas中切換圖片的動(dòng)畫
還是利用上面的那種飛機(jī)圖.我現(xiàn)在想實(shí)現(xiàn)一個(gè)從第一架完整飛機(jī)變化到最后一架爆炸飛機(jī)的效果.
那么有心的小伙就會(huì)發(fā)現(xiàn)了,在js中我們想實(shí)現(xiàn)圖片的切換,只要改變背景圖的background-position就可以了,那么在canvas中利用的就是requestAnimationFrame配合drawImage了.
只要不停的改變截取圖片的位置就可以了.
我們來(lái)看下面的demo1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>爆炸飛機(jī)切換</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
let windowW = document.body.clientWidth
let windowH = document.body.clientHeight
let canvas = document.querySelector('#canvas')
canvas.width = windowW
canvas.height = windowH
let ctx = canvas.getContext('2d')
let frame = 0 //幀數(shù)
let img1 = new Image()
img1.src = 'img/herofly.png'
//定義變量:圖片截取的位置(x,y) 圖片截取的寬高(w,h) 整張大圖的寬度, 截取的飛機(jī)在canvas中的位置(iX, iY)
let x = 0, y = 0, w = 66, h = 82, img1W = 330, iX = 0, iY = 0;
animate()
function animate() {
//定義一個(gè)幀數(shù)的變量,函數(shù)每一幀執(zhí)行一次,則frame就加一次,以此記錄幀數(shù)
frame++
ctx.clearRect(0, 0, canvas.width, canvas.height)
//每過20幀執(zhí)行一次 x += w 以此達(dá)到切換圖片的效果
if(frame % 20 === 0 ) {
x += w
if (x >= img1W - w) { //判定當(dāng)走到最后一張爆炸圖的時(shí)候,讓x又等于0, 達(dá)到無(wú)限動(dòng)畫的效果
x = 0
}
}
//每隔一幀就執(zhí)行繪畫飛機(jī)的操作
ctx.drawImage(img1, x, y, w, h, iX, iY, w, h)
//為避免frame加到太大,在這里做一個(gè)當(dāng)frame加到10000時(shí),又讓它為0的操作
if(frame > 10000 ) {
frame = 0
}
//利用requestAnimationFrame達(dá)到動(dòng)畫效果
requestAnimationFrame(animate)
}
</script>
</body>
</html>
3.3 canvas中圖片運(yùn)動(dòng)的動(dòng)畫
上面我們介紹的是在一張大圖中,持續(xù)改變它截取圖片的位置(也就是x, y ),來(lái)達(dá)到切換圖片的效果.這種轉(zhuǎn)化有些類似于"靜態(tài)的轉(zhuǎn)化".
那么怎樣讓圖片在canvas中移動(dòng)呢,改變的就是我們drawImage()中的第6,7個(gè)參數(shù)(也就是截取下來(lái)的圖片在canvas中的位置)
還是利用demo1中的那張飛機(jī)圖,只不過這次我不讓它"爆炸"了(不進(jìn)行圖片切換),而是讓它從canvas的最下邊飛到最上邊
來(lái)看demo2:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<title>爆炸飛機(jī)切換</title>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
let windowW = document.body.clientWidth
let windowH = document.body.clientHeight
let canvas = document.querySelector('#canvas')
canvas.width = windowW
canvas.height = windowH
let ctx = canvas.getContext('2d')
let frame = 0 //幀數(shù)
let img1 = new Image()
img1.src = 'img/herofly.png'
//定義變量:圖片截取的位置(x,y) 圖片截取的寬高(w,h) 整張大圖的寬度, 截取的飛機(jī)在canvas中的位置(iX, iY)
let x = 0, y = 0, w = 66, h = 82, img1W = 330, iX = 0, iY = canvas.height - h;
animate()
function animate() {
//定義一個(gè)幀數(shù)的變量,函數(shù)每一幀執(zhí)行一次,則frame就加一次,以此記錄幀數(shù)
frame++
ctx.clearRect(0, 0, canvas.width, canvas.height)
//每過20幀執(zhí)行一次 iY -= 4 以此達(dá)到圖片運(yùn)動(dòng)的效果
if(frame % 20 === 0 ) {
iY -= 4
if (iY <= 0) { //判定當(dāng)飛機(jī)運(yùn)動(dòng)到最上邊的時(shí)候,讓iY又等于畫布的高 - 飛機(jī)的高, 達(dá)到無(wú)限動(dòng)畫的效果
iY = canvas.height - h
}
}
//每隔一幀就執(zhí)行繪畫飛機(jī)的操作
ctx.drawImage(img1, x, y, w, h, iX, iY, w, h)
//為避免frame加到太大,在這里做一個(gè)當(dāng)frame加到10000時(shí),又讓它為0的操作
if(frame > 10000 ) {
frame = 0
}
//利用requestAnimationFrame達(dá)到動(dòng)畫效果
requestAnimationFrame(animate)
}
</script>
</body>
</html>
可以看到上面的demo2 和 demo1 大致相同,只不過此時(shí)改變的是iY而已.
3.4 canvas中的視頻
在頁(yè)面中,插入一段視頻,只需要使用<video src="video1.mp4"></video>標(biāo)簽
而在canvas中我們只需要將視頻當(dāng)圖片一樣插入,在利用canvas中的動(dòng)畫讓它達(dá)到播放的效果.
例1:
<body>
<div class="out">
<video id="video1" src="img/xiaoyin.mp4" style="width:300px;" autoplay></video>
<canvas id="myCanvas" width="1000" height="300"></canvas>
</div>
<script>
let canvas = document.querySelector("#myCanvas")
let ctx = canvas.getContext("2d")
let imgObj = document.querySelector('#video1')
function play(){
ctx.drawImage(imgObj, 0, 0,canvas.width,canvas.height)
window.requestAnimationFrame(play);
}
play()
</script>
</body>
此時(shí)頁(yè)面中出現(xiàn)的應(yīng)該是倆個(gè)視頻,并且用canvas繪制出來(lái)的視頻并不會(huì)卡頓,效果和直接用video的一樣,要是你想只顯示canvas的視頻的話,可以將video1給display:none掉.
效果圖:
3.5 灰色視頻
在介紹講解灰色視頻之前,我想先介紹一個(gè)很牛x的方法getImageData()
,這個(gè)方法能獲取整張圖片,或者一片圖片區(qū)域的所有信息.
用法為:
ctx.drawImage(imgObj, 0, 0,canvas.width,canvas.height)
var imageData = ctx.getImageData(0,0, canvas.width, canvas.height);
來(lái)看下面這個(gè)小例子,點(diǎn)擊按鈕生成,將左側(cè)彩色的圖片變?yōu)榛疑?
點(diǎn)擊生成:
<body>
<div class="out">
<canvas id="canvas" width="300" height="400"></canvas>
<img id="PutImg" src="" alt="">
<input id="btn" type="button" value="生成">
</div>
<script>
let out = document.querySelector('.out')
let btn = document.querySelector('#btn')
let PutImg = document.querySelector('#PutImg')
let canvas = document.querySelector('#canvas')
let ctx = canvas.getContext('2d')
let img = new Image()
img.src = 'img/01.jpg'
img.onload=function () {
ctx.drawImage(img,0,0,canvas.width,canvas.height)
btn.onclick = function () {
var imageData = ctx.getImageData(0,0, canvas.width, canvas.height);
console.log(imageData);
var pixels = imageData.data;
//遍歷像素點(diǎn)
for (var i=0; i<pixels.length; i+=4){
var r = pixels[i];
var g = pixels[i+1];
var b = pixels[i+2];
//獲取灰色
var gray = parseInt((r+g+b)/3);
pixels[i] = gray;
pixels[i+1] = gray;
pixels[i+2] = gray;
}
ctx.putImageData(imageData, 0,0);
let url = canvas.toDataURL()
PutImg.src = url
ctx.clearRect(0,0,canvas.width,canvas.height)
}
}
</script>
</body>
我們可以將上面獲取到的imageData對(duì)象打印出來(lái)看下:
這個(gè)imageData對(duì)象中有3個(gè)屬性,分別是data,高度,寬度
那么這個(gè)data可以看出是一個(gè)數(shù)組,而且是一個(gè)長(zhǎng)度為480000的數(shù)組
那么這個(gè)數(shù)組是怎么來(lái)的呢.
其實(shí)這個(gè)數(shù)組存儲(chǔ)的是所有像素點(diǎn)的顏色信息
你可以理解為,我的這張圖片是300x400像素的,也就是有120000個(gè)像素點(diǎn),而一個(gè)像素點(diǎn)的顏色(也就是rgba) 是由個(gè)值組成的,分別是r,g,b,a的值
也就是說數(shù)組中每4個(gè)值代表的就是一個(gè)像素點(diǎn)的信息.
如前4個(gè)值[134,134,134,225] 表示的就是第一個(gè)像素點(diǎn)(最左上角的)的信息.
所以在做灰色處理時(shí),我們只需要將每個(gè)像素點(diǎn)的前三個(gè)值全部一樣的就可以了,然后在利用putImageData()方法來(lái)輸出一下處理好的圖片.
而視頻的處理也是一樣的
在例1的基礎(chǔ)上加以改進(jìn):
<body>
<div class="out">
<video id="video1" src="img/xiaoyin.mp4" style="width:300px;" autoplay></video>
<canvas id="myCanvas" width="1000" height="300"></canvas>
</div>
<script>
let canvas = document.querySelector("#myCanvas")
let ctx = canvas.getContext("2d")
let imgObj = document.querySelector('#video1')
function play(){
ctx.drawImage(imgObj, 0, 0,canvas.width,canvas.height)
var imageData = ctx.getImageData(0,0, canvas.width, canvas.height);
var pixels = imageData.data;
//遍歷像素點(diǎn)
for (var i=0; i<pixels.length; i+=4){
var r = pixels[i];
var g = pixels[i+1];
var b = pixels[i+2];
//獲取灰色
var gray = parseInt((r+g+b)/3);
pixels[i] = gray;
pixels[i+1] = gray;
pixels[i+2] = gray;
}
ctx.putImageData(imageData, 0,0);
window.requestAnimationFrame(play);
}
play()
</script>
</body>
此時(shí)我們的視頻就變成灰色的了
效果圖: