前面的話
本文將從最基本的imageData對(duì)象的理論知識(shí)說(shuō)開(kāi)去,詳細(xì)介紹canvas粒子系統(tǒng)的構(gòu)建
imageData
關(guān)于圖像數(shù)據(jù)imageData共有3個(gè)方法,包括getImageData()、putImageData()、createImageData()
【getImageData()】
2D上下文可以通過(guò)getImageData()取得原始圖像數(shù)據(jù)。這個(gè)方法接收4個(gè)參數(shù):畫(huà)面區(qū)域的x和y坐標(biāo)以及該區(qū)域的像素寬度和高度
例如,要取得左上角坐標(biāo)為(10,5)、大小為50*50像素的區(qū)域的圖像數(shù)據(jù),可以使用以下代碼:
var imageData = context.getImageData(10,5,50,50);
返回的對(duì)象是ImageData的實(shí)例,每個(gè)ImageData對(duì)象有3個(gè)屬性:width/height/data
1、width:表示imageData對(duì)角的寬度
2、height:表示imageData對(duì)象的高度
3、data是一個(gè)數(shù)組,保存著圖像中每一個(gè)像素的數(shù)據(jù)。在data數(shù)組中,每一個(gè)像素用4個(gè)元素來(lái)保存,分別表示red、green、blue、透明度
[注意]圖像中有多少像素,data的長(zhǎng)度就等于像素個(gè)數(shù)乘以4
//第一個(gè)像素如下var data = imageData.data;var red = data[0];var green = data[1]; var blue = data[2];var alpha = data[3];
數(shù)組中每個(gè)元素的值是在0-255之間,能夠直接訪問(wèn)到原始圖像數(shù)據(jù),就能夠以各種方式來(lái)操作這些數(shù)據(jù)
[注意]如果要使用getImageData()獲取的canvas中包含drawImage()方法,則該方法中的URL不能跨域
【createImageData()】
createImageData(width,height)方法創(chuàng)建新的空白ImageData對(duì)象。新對(duì)象的默認(rèn)像素值 transparent black,相當(dāng)于rgba(0,0,0,0)
var imgData = context.createImageData(100,100);
【putImageData()】
putImageData()方法將圖像數(shù)據(jù)從指定的ImageData對(duì)象放回畫(huà)布上,該方法共有以下參數(shù)
imgData:要放回畫(huà)布的ImageData對(duì)象(必須)x:imageData對(duì)象的左上角的x坐標(biāo)(必須)y:imageData對(duì)象的左上角的y坐標(biāo)(必須)dirtyX:在畫(huà)布上放置圖像的水平位置(可選)dirtyY:在畫(huà)布上放置圖像的垂直位置(可選)dirtyWidth:在畫(huà)布上繪制圖像所使用的寬度(可選)dirtyHeight:在畫(huà)布上繪制圖像所使用的高度(可選)
[注意]參數(shù)3到7要么都沒(méi)有,要么都存在
context.putImageData(imgData,0,0);
context.putImageData(imgData,0,0,50,50,200,200);
粒子寫(xiě)入
粒子,指圖像數(shù)據(jù)imageData中的每一個(gè)像素點(diǎn)。下面以一個(gè)簡(jiǎn)易實(shí)例來(lái)說(shuō)明完全寫(xiě)入與粒子寫(xiě)入
【完全寫(xiě)入】
200*200的canvas1中存在文字'小火柴',并將canvas1整個(gè)作為圖像數(shù)據(jù)寫(xiě)入同樣尺寸的canvas2中
<canvas id="drawing1" style="border:1px solid black"></canvas><canvas id="drawing2" style="border:1px solid black"></canvas><script>var drawing1 = document.getElementById('drawing1');var drawing2 = document.getElementById('drawing2');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var cxt2 = drawing2.getContext('2d'); var W = drawing1.width = drawing2.width = 200; var H = drawing1.height = drawing2.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); //寫(xiě)入drawing2中 cxt2.putImageData(imageData,0,0);</script>【粒子寫(xiě)入】
對(duì)于完全寫(xiě)入而言,相當(dāng)于只是簡(jiǎn)單的復(fù)制粘貼,如果要對(duì)每個(gè)像素點(diǎn)進(jìn)行精細(xì)地控制,則需要使用粒子寫(xiě)入。canvas1中存在著大量的空白區(qū)域,只有'小火柴'這三個(gè)字的區(qū)域是有效的。于是,可以根據(jù)圖像數(shù)據(jù)imageData中的透明度對(duì)粒子進(jìn)行篩選,只篩選出透明度大于0的粒子
<canvas id="drawing1" style="border:1px solid black"></canvas><canvas id="drawing2" style="border:1px solid black"></canvas><script>var drawing1 = document.getElementById('drawing1');var drawing2 = document.getElementById('drawing2');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var cxt2 = drawing2.getContext('2d'); var W = drawing1.width = drawing2.width = 200; var H = drawing1.height = drawing2.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); //寫(xiě)入drawing2中 cxt2.putImageData(setData(imageData),0,0); function setData(imageData){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; for(var i = 0; i < W; i++){ for(var j = 0; j < H ;j++){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); } } } //40000 2336 console.log(i*j,dots.length); //新建一個(gè)imageData,并將篩選后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < dots.length; i++){ oNewImage.data[dots[i]+0] = imageData.data[dots[i]+0]; oNewImage.data[dots[i]+1] = imageData.data[dots[i]+1]; oNewImage.data[dots[i]+2] = imageData.data[dots[i]+2]; oNewImage.data[dots[i]+3] = imageData.data[dots[i]+3]; } return oNewImage; }}</script>雖然結(jié)果看上去相同,但canvas2只使用了canvas1中40000個(gè)粒子中的2336個(gè)
粒子篩選
當(dāng)粒子完全寫(xiě)入時(shí),與canvas復(fù)制粘貼的效果相同。而當(dāng)粒子有所篩選時(shí),則會(huì)出現(xiàn)一些奇妙的效果
【按序篩選】
由于取得粒子時(shí),使用的是寬度值*高度值的雙重循環(huán),且都以加1的形式遞增。如果不是加1,而是加n,則可以實(shí)現(xiàn)按序篩選的效果
<canvas id="drawing1" style="border:1px solid black"></canvas><canvas id="drawing2" style="border:1px solid black"></canvas><div id="con"> <button>1</button> <button>2</button> <button>3</button> <button>4</button> <button>5</button></div><script>var oCon = document.getElementById('con');oCon.onclick = function(e){ e = e || event; var tempN = e.target.innerHTML; if(tempN){ cxt2.clearRect(0,0,W,H); cxt2.putImageData(setData(imageData,Number(tempN)),0,0); }}var drawing1 = document.getElementById('drawing1');var drawing2 = document.getElementById('drawing2');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var cxt2 = drawing2.getContext('2d'); var W = drawing1.width = drawing2.width = 200; var H = drawing1.height = drawing2.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); //寫(xiě)入drawing2中 cxt2.putImageData(setData(imageData,1),0,0); function setData(imageData,n){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); } } } //新建一個(gè)imageData,并將篩選后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < dots.length; i++){ oNewImage.data[dots[i]+0] = imageData.data[dots[i]+0]; oNewImage.data[dots[i]+1] = imageData.data[dots[i]+1]; oNewImage.data[dots[i]+2] = imageData.data[dots[i]+2]; oNewImage.data[dots[i]+3] = imageData.data[dots[i]+3]; } return oNewImage; }}</script>【隨機(jī)篩選】
除了使用按序篩選,還可以使用隨機(jī)篩選。 通過(guò)雙重循環(huán)得到的粒子的位置信息,放到dots數(shù)組中。通過(guò)splice()方法進(jìn)行篩選,將篩選后的位置信息放到新建的newDots數(shù)組中,然后再使用createImageData(),新建一個(gè)圖像數(shù)據(jù)對(duì)象并返回
<canvas id="drawing1" style="border:1px solid black"></canvas><canvas id="drawing2" style="border:1px solid black"></canvas><div id="con"> <button>1000</button> <button>2000</button> <button>3000</button> <button>4000</button></div><script>var oCon = document.getElementById('con');oCon.onclick = function(e){ e = e || event; var tempN = e.target.innerHTML; if(tempN){ cxt2.clearRect(0,0,W,H); cxt2.putImageData(setData(imageData,1,Number(tempN)),0,0); }}var drawing1 = document.getElementById('drawing1');var drawing2 = document.getElementById('drawing2');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var cxt2 = drawing2.getContext('2d'); var W = drawing1.width = drawing2.width = 200; var H = drawing1.height = drawing2.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); //寫(xiě)入drawing2中 cxt2.putImageData(setData(imageData,1),0,0); function setData(imageData,n,m){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); } } } //篩選粒子,僅保存m個(gè)到newDots數(shù)組中。如果不傳入m,則不進(jìn)行篩選 var newDots = []; if(m && (dots.length > m)){ for(var i = 0; i < m; i++){ newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1))); } }else{ newDots = dots; } //新建一個(gè)imageData,并將篩選后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < newDots.length; i++){ oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0]; oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1]; oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2]; oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3]; } return oNewImage; }}</script>像素顯字
下面來(lái)使用粒子篩選來(lái)實(shí)現(xiàn)一個(gè)像素顯字的效果。像素顯字即從不清晰的效果逐步過(guò)渡到完全顯示
【按序像素顯字】
按序像素顯字的實(shí)現(xiàn)原理非常簡(jiǎn)單,比如,共有2000個(gè)粒子,共10個(gè)程度的過(guò)渡效果。則使用10個(gè)數(shù)組,分別保存200,400,600,800,100,1200,1400,1600,1800和2000個(gè)粒子。然后使用定時(shí)器將其逐步顯示出來(lái)即可
<canvas id="drawing1" style="border:1px solid black"></canvas><button id="btn">開(kāi)始顯字</button><script>var drawing1 = document.getElementById('drawing1');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); //獲得10組粒子 var imageDataArr = []; var n = 10; var index = 0; for(var i = n; i > 0; i--){ imageDataArr.push(setData(imageData,i)); } var oTimer = null; btn.onclick = function(){ clearTimeout(oTimer); showData(); } function showData(){ oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); //寫(xiě)入drawing1中 cxt.putImageData(imageDataArr[index++],0,0); //迭代函數(shù) showData(); if(index == 10){ index = 0; clearTimeout(oTimer); } },100); } function setData(imageData,n,m){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); } } } //篩選粒子,僅保存m個(gè)到newDots數(shù)組中。如果不傳入m,則不進(jìn)行篩選 var newDots = []; if(m && (dots.length > m)){ for(var i = 0; i < m; i++){ newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1))); } }else{ newDots = dots; } //新建一個(gè)imageData,并將篩選后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < newDots.length; i++){ oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0]; oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1]; oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2]; oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3]; } return oNewImage; }}</script>【隨機(jī)像素顯字】
隨機(jī)像素顯字的原理類(lèi)似,保存多個(gè)不同數(shù)量的隨機(jī)像素的數(shù)組即可
<canvas id="drawing1" style="border:1px solid black"></canvas><button id="btn">開(kāi)始顯字</button><script>var drawing1 = document.getElementById('drawing1');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); //獲得10組粒子 var imageDataArr = []; var n = 10; var index = 0; for(var i = n; i > 0; i--){ imageDataArr.push(setData(imageData,1,i)); } var oTimer = null; btn.onclick = function(){ clearTimeout(oTimer); showData(); } function showData(){ oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); //寫(xiě)入drawing1中 cxt.putImageData(imageDataArr[index++],0,0); //迭代函數(shù) showData(); if(index == 10){ clearTimeout(oTimer); index = 0; } },100); } function setData(imageData,n,m){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); } } } //篩選粒子,僅保存dots.length/m個(gè)到newDots數(shù)組中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(Number(dots.splice(Math.floor(Math.random()*dots.length),1))); } //新建一個(gè)imageData,并將篩選后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < newDots.length; i++){ oNewImage.data[newDots[i]+0] = imageData.data[newDots[i]+0]; oNewImage.data[newDots[i]+1] = imageData.data[newDots[i]+1]; oNewImage.data[newDots[i]+2] = imageData.data[newDots[i]+2]; oNewImage.data[newDots[i]+3] = imageData.data[newDots[i]+3]; } return oNewImage; }}</script>粒子動(dòng)畫(huà)
粒子動(dòng)畫(huà)并不是粒子在做動(dòng)畫(huà),而是通過(guò)getImageData()方法獲得粒子的隨機(jī)坐標(biāo)和最終坐標(biāo)后,通過(guò)fillRect()方法繪制的小方塊在做運(yùn)動(dòng)。使用定時(shí)器,不斷的繪制坐標(biāo)變化的小方塊,以此來(lái)產(chǎn)生運(yùn)動(dòng)的效果
【隨機(jī)位置】
<canvas id="drawing1" style="border:1px solid black"></canvas><button id="btn1">開(kāi)始顯字</button><button id="btn2">重新混亂</button><script>var drawing1 = document.getElementById('drawing1');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); function setData(imageData,n,m){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'randomX':Math.random()*W, 'randomY':Math.random()*H, } } } } //篩選粒子,僅保存dots.length/m個(gè)到newDots數(shù)組中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } //獲得粒子數(shù)組 var dataArr = setData(imageData,1,1); var oTimer1 = null; var oTimer2 = null; btn1.onclick = function(){ clearTimeout(oTimer1); showData(10); } btn2.onclick = function(){ clearTimeout(oTimer2); showRandom(10); } function showData(n){ oTimer1 = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = temp.randomX; var y0 = temp.randomY; var disX = temp.x - temp.randomX; var disY = temp.y - temp.randomY; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } showData(n-1); if(n === 1){ clearTimeout(oTimer1); } },60); } function showRandom(n){ oTimer2 = setTimeout(function fn(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = temp.x; var y0 = temp.y; var disX = temp.randomX - temp.x; var disY = temp.randomY - temp.y; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } showRandom(n-1); if(n === 1){ clearTimeout(oTimer2); } },60); } }</script>【飄入效果】
飄入效果與隨機(jī)顯字的原理相似,不再贅述
<canvas id="drawing1" style="border:1px solid black"></canvas><button id="btn1">左上角飄入</button><script>var drawing1 = document.getElementById('drawing1');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); function setData(imageData,n,m){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'randomX':Math.random()*W, 'randomY':Math.random()*H, } } } } //篩選粒子,僅保存dots.length/m個(gè)到newDots數(shù)組中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } //獲得粒子數(shù)組 var dataArr = setData(imageData,1,1); var oTimer1 = null; btn1.onclick = function(){ clearTimeout(oTimer1); showData(10); } function showData(n){ oTimer1 = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = 0; var y0 = 0; var disX = temp.x - 0; var disY = temp.y - 0; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } showData(n-1); if(n === 1){ clearTimeout(oTimer1); } },60); } }</script>鼠標(biāo)交互
一般地,粒子的鼠標(biāo)交互都與isPointInPath(x,y)方法有關(guān)
【移入變色】
當(dāng)鼠標(biāo)接近粒子時(shí),該粒子變紅。實(shí)現(xiàn)原理很簡(jiǎn)單。鼠標(biāo)移動(dòng)時(shí),通過(guò)isPointInPath(x,y)方法檢測(cè),有哪些粒子處于當(dāng)前指針?lè)秶鷥?nèi)。如果處于,繪制1像素的紅色矩形即可
<canvas id="drawing1" style="border:1px solid black"></canvas><script>var drawing1 = document.getElementById('drawing1');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); function setData(imageData,n,m){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'randomX':Math.random()*W, 'randomY':Math.random()*H, } } } } //篩選粒子,僅保存dots.length/m個(gè)到newDots數(shù)組中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } //獲得粒子數(shù)組 var dataArr = setData(imageData,1,1); //鼠標(biāo)移動(dòng)時(shí),當(dāng)粒子距離鼠標(biāo)指針小于10時(shí),則進(jìn)行相關(guān)操作 drawing1.onmousemove = function(e){ e = e || event; var x = e.clientX - drawing1.getBoundingClientRect().left; var y = e.clientY - drawing1.getBoundingClientRect().top; cxt.beginPath(); cxt.arc(x,y,10,0,Math.PI*2); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(cxt.isPointInPath(temp.x,temp.y)){ cxt.fillStyle = 'red'; cxt.fillRect(temp.x,temp.y,1,1); } } }}</script>【遠(yuǎn)離鼠標(biāo)】
鼠標(biāo)點(diǎn)擊時(shí),以鼠標(biāo)指針為圓心的一定范圍內(nèi)的粒子需要移動(dòng)到該范圍以外。一段時(shí)間后,粒子回到原始位置
實(shí)現(xiàn)原理并不復(fù)雜,使用isPointInPath(x,y)方法即可,如果粒子處于當(dāng)前路徑中,則沿著鼠標(biāo)指針與粒子坐標(biāo)組成的直線方向,移動(dòng)到路徑的邊緣
<canvas id="drawing1" style="border:1px solid black"></canvas><script>var drawing1 = document.getElementById('drawing1');if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 200; var H = drawing1.height = 200; var str = '小火柴'; cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } //渲染文字 cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); //獲取imageData var imageData = cxt.getImageData(0,0,W,H); cxt.clearRect(0,0,W,H); function setData(imageData,n,m){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'randomX':Math.random()*W, 'randomY':Math.random()*H, 'mark':false } } } } //篩選粒子,僅保存dots.length/m個(gè)到newDots數(shù)組中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } //獲得粒子數(shù)組 var dataArr = setData(imageData,2,1); //將篩選后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < dataArr.length; i++){ for(var j = 0; j < 4; j++){ oNewImage.data[dataArr[i].red+j] = imageData.data[dataArr[i].red+j]; } } //寫(xiě)入canvas中 cxt.putImageData(oNewImage,0,0); //設(shè)置鼠標(biāo)檢測(cè)半徑為r var r = 20; //鼠標(biāo)移動(dòng)時(shí),當(dāng)粒子距離鼠標(biāo)指針小于20時(shí),則進(jìn)行相關(guān)操作 drawing1.onmousedown = function(e){ e = e || event; var x = e.clientX - drawing1.getBoundingClientRect().left; var y = e.clientY - drawing1.getBoundingClientRect().top; cxt.beginPath(); cxt.arc(x,y,r,0,Math.PI*2); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(cxt.isPointInPath(temp.x,temp.y)){ temp.mark = true; var angle = Math.atan2((temp.y - y),(temp.x - x)); temp.endX = x - r*Math.cos(angle); temp.endY = y - r*Math.sin(angle); var disX = temp.x - temp.endX; var disY = temp.y - temp.endY; cxt.fillStyle = '#fff'; cxt.fillRect(temp.x,temp.y,1,1); cxt.fillStyle = '#000'; cxt.fillRect(temp.endX,temp.endY,1,1); dataRecovery(10); }else{ temp.mark = false; } } var oTimer = null; function dataRecovery(n){ clearTimeout(oTimer); oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(temp.mark){ var x0 = temp.endX; var y0 = temp.endY; var disX = temp.x - x0; var disY = temp.y - y0; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); }else{ cxt.fillRect(temp.x,temp.y,1,1); } } dataRecovery(n-1); if(n === 1){ clearTimeout(oTimer); } },17); } } }</script>綜合實(shí)例
下面將上面的效果制作為一個(gè)可編輯的綜合實(shí)例
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title></head><body><canvas id="drawing1" style="border:1px solid black"></canvas><br><div style="margin-bottom:10px"> <span>粒子設(shè)置:</span> <input type="text" id="textValue" value="小火柴的藍(lán)色理想"> <button id="btnSetText">文字設(shè)置確認(rèn)</button> <button id="btnchoose2">按序篩選</button> <button id="btnchoose3">隨機(jī)篩選</button> <button id="btnchoose1">不篩選</button> </div><div style="margin-bottom:10px"> <span>粒子效果:</span> <button id="btn1">按序顯字</button> <button id="btn2">隨機(jī)顯字</button> <button id="btn3">混亂聚合</button> <button id="btn4">重新混亂</button></div><div> <span>鼠標(biāo)效果:</span> <span>1、鼠標(biāo)移到文字上時(shí),文字顏色變紅;</span> <span>2、鼠標(biāo)在文字上點(diǎn)擊時(shí),粒子遠(yuǎn)離鼠標(biāo)指針</span></div><script>if(drawing1.getContext){ var cxt = drawing1.getContext('2d'); var W = drawing1.width = 300; var H = drawing1.height = 200; var imageData; var dataArr; btnSetText.onclick = function(){ fnSetText(textValue.value); } function fnSetText(str){ cxt.clearRect(0,0,W,H); cxt.textBaseline = 'top'; var sh = 60; cxt.font = sh + 'px 宋體' var sw = cxt.measureText(str).width; if(sw > W){ sw = W; } cxt.fillText(str,(W - sw)/2,(H - sh)/2,W); imageData = cxt.getImageData(0,0,W,H); dataArr = setData(imageData,1,1); } fnSetText('小火柴'); btnchoose1.onclick = function(){ dataArr = setData(imageData,1,1); saveData(dataArr); } btnchoose2.onclick = function(){ dataArr = setData(imageData,2,1); saveData(dataArr); } btnchoose3.onclick = function(){ dataArr = setData(imageData,1,2); saveData(dataArr); } //篩選粒子 function setData(imageData,n,m){ //從imageData對(duì)象中取得粒子,并存儲(chǔ)到dots數(shù)組中 var dots = []; //dots的索引 var index = 0; for(var i = 0; i < W; i+=n){ for(var j = 0; j < H ;j+=n){ //data值中的紅色值 var k = 4*(i + j*W); //data值中的透明度 if(imageData.data[k+3] > 0){ //將透明度大于0的data中的紅色值保存到dots數(shù)組中 dots.push(k); dots[index++] = { 'index':index, 'x':i, 'y':j, 'red':k, 'green':k+1, 'blue':k+2, 'randomX':Math.random()*W, 'randomY':Math.random()*H, 'mark':false } } } } //篩選粒子,僅保存dots.length/m個(gè)到newDots數(shù)組中 var newDots = []; var len = Math.floor(dots.length/m); for(var i = 0; i < len; i++){ newDots.push(dots.splice(Math.floor(Math.random()*dots.length),1)[0]); } return newDots; } function saveData(dataArr){ //將篩選后的粒子信息保存到新建的imageData中 var oNewImage = cxt.createImageData(W,H); for(var i = 0; i < dataArr.length; i++){ for(var j = 0; j < 4; j++){ oNewImage.data[dataArr[i].red+j] = imageData.data[dataArr[i].red+j]; } } //寫(xiě)入canvas中 cxt.putImageData(oNewImage,0,0); } //顯示粒子 function showData(arr,oTimer,index,n){ oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); //寫(xiě)入canvas中 saveData(arr[index++]); if(index == n){ clearTimeout(oTimer); }else{ //迭代函數(shù) showData(arr,oTimer,index,n); } },60); } //重新混亂 function showDataToRandom(dataArr,oTimer,n){ oTimer = setTimeout(function fn(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = temp.x; var y0 = temp.y; var disX = temp.randomX - temp.x; var disY = temp.randomY - temp.y; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } n--; if(n === 0){ clearTimeout(oTimer); }else{ showDataToRandom(dataArr,oTimer,n); } },60); } //混亂聚合 function showRandomToData(dataArr,oTimer,n){ oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; var x0 = temp.randomX; var y0 = temp.randomY; var disX = temp.x - temp.randomX; var disY = temp.y - temp.randomY; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); } n--; if(n === 0){ clearTimeout(oTimer); }else{ showRandomToData(dataArr,oTimer,n); } },60); } btn1.onclick = function(){ btn1.arr = []; for(var i = 10; i > 1; i--){ btn1.arr.push(setData(imageData,i,1)); } showData(btn1.arr,btn1.oTimer,0,9); } btn2.onclick = function(){ btn2.arr = []; for(var i = 10; i > 0; i--){ btn2.arr.push(setData(imageData,2,i)); } showData(btn2.arr,btn2.oTimer,0,10); } btn3.onclick = function(){ clearTimeout(btn3.oTimer); showRandomToData(dataArr,btn3.oTimer,10); } btn4.onclick = function(){ clearTimeout(btn4.oTimer); showDataToRandom(dataArr,btn4.oTimer,10); } //鼠標(biāo)移動(dòng) drawing1.onmousemove = function(e){ e = e || event; var x = e.clientX - drawing1.getBoundingClientRect().left; var y = e.clientY - drawing1.getBoundingClientRect().top; cxt.beginPath(); cxt.arc(x,y,10,0,Math.PI*2); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(cxt.isPointInPath(temp.x,temp.y)){ cxt.fillStyle = 'red'; cxt.fillRect(temp.x,temp.y,1,1); } } cxt.fillStyle = 'black'; } //鼠標(biāo)點(diǎn)擊 drawing1.onmousedown = function(e){ var r = 20; e = e || event; var x = e.clientX - drawing1.getBoundingClientRect().left; var y = e.clientY - drawing1.getBoundingClientRect().top; cxt.beginPath(); cxt.arc(x,y,r,0,Math.PI*2); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(cxt.isPointInPath(temp.x,temp.y)){ temp.mark = true; var angle = Math.atan2((temp.y - y),(temp.x - x)); temp.endX = x - r*Math.cos(angle); temp.endY = y - r*Math.sin(angle); var disX = temp.x - temp.endX; var disY = temp.y - temp.endY; cxt.fillStyle = '#fff'; cxt.fillRect(temp.x,temp.y,1,1); cxt.fillStyle = '#f00'; cxt.fillRect(temp.endX,temp.endY,1,1); cxt.fillStyle="#000"; dataRecovery(10); }else{ temp.mark = false; } } var oTimer = null; function dataRecovery(n){ clearTimeout(oTimer); oTimer = setTimeout(function(){ cxt.clearRect(0,0,W,H); for(var i = 0; i < dataArr.length; i++){ var temp = dataArr[i]; if(temp.mark){ var x0 = temp.endX; var y0 = temp.endY; var disX = temp.x - x0; var disY = temp.y - y0; cxt.fillRect(x0 + disX/n,y0 + disY/n,1,1); }else{ cxt.fillRect(temp.x,temp.y,1,1); } } dataRecovery(n-1); if(n === 1){ clearTimeout(oTimer); } },17); } } }</script> </body></html>以上這篇基于canvas粒子系統(tǒng)的構(gòu)建詳解就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持VeVb武林網(wǎng)。
新聞熱點(diǎn)
疑難解答