基于canvas粒子系統(tǒng)的構(gòu)建詳解
前面的話
本文將從最基本的imageData對象的理論知識說開去,詳細(xì)介紹canvas粒子系統(tǒng)的構(gòu)建
imageData
關(guān)于圖像數(shù)據(jù)imageData共有3個方法,包括getImageData()、putImageData()、createImageData()
【getImageData()】
2D上下文可以通過getImageData()取得原始圖像數(shù)據(jù)。這個方法接收4個參數(shù):畫面區(qū)域的x和y坐標(biāo)以及該區(qū)域的像素寬度和高度
例如,要取得左上角坐標(biāo)為(10,5)、大小為50*50像素的區(qū)域的圖像數(shù)據(jù),可以使用以下代碼:
var imageData = context.getImageData(10,5,50,50);
返回的對象是ImageData的實(shí)例,每個ImageData對象有3個屬性:width\height\data
1、width:表示imageData對角的寬度
2、height:表示imageData對象的高度
3、data是一個數(shù)組,保存著圖像中每一個像素的數(shù)據(jù)。在data數(shù)組中,每一個像素用4個元素來保存,分別表示red、green、blue、透明度
[注意]圖像中有多少像素,data的長度就等于像素個數(shù)乘以4
//第一個像素如下 var data = imageData.data; var red = data[0]; var green = data[1]; var blue = data[2]; var alpha = data[3];
數(shù)組中每個元素的值是在0-255之間,能夠直接訪問到原始圖像數(shù)據(jù),就能夠以各種方式來操作這些數(shù)據(jù)
[注意]如果要使用getImageData()獲取的canvas中包含drawImage()方法,則該方法中的URL不能跨域
【createImageData()】
createImageData(width,height)方法創(chuàng)建新的空白ImageData對象。新對象的默認(rèn)像素值 transparent black,相當(dāng)于rgba(0,0,0,0)
var imgData = context.createImageData(100,100);
【putImageData()】
putImageData()方法將圖像數(shù)據(jù)從指定的ImageData對象放回畫布上,該方法共有以下參數(shù)
imgData:要放回畫布的ImageData對象(必須) x:imageData對象的左上角的x坐標(biāo)(必須) y:imageData對象的左上角的y坐標(biāo)(必須) dirtyX:在畫布上放置圖像的水平位置(可選) dirtyY:在畫布上放置圖像的垂直位置(可選) dirtyWidth:在畫布上繪制圖像所使用的寬度(可選) dirtyHeight:在畫布上繪制圖像所使用的高度(可選)
[注意]參數(shù)3到7要么都沒有,要么都存在
context.putImageData(imgData,0,0);
context.putImageData(imgData,0,0,50,50,200,200);
粒子寫入
粒子,指圖像數(shù)據(jù)imageData中的每一個像素點(diǎn)。下面以一個簡易實(shí)例來說明完全寫入與粒子寫入
【完全寫入】
200*200的canvas1中存在文字'小火柴',并將canvas1整個作為圖像數(shù)據(jù)寫入同樣尺寸的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);
//寫入drawing2中
cxt2.putImageData(imageData,0,0);
</script>
【粒子寫入】
對于完全寫入而言,相當(dāng)于只是簡單的復(fù)制粘貼,如果要對每個像素點(diǎn)進(jìn)行精細(xì)地控制,則需要使用粒子寫入。canvas1中存在著大量的空白區(qū)域,只有'小火柴'這三個字的區(qū)域是有效的。于是,可以根據(jù)圖像數(shù)據(jù)imageData中的透明度對粒子進(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);
//寫入drawing2中
cxt2.putImageData(setData(imageData),0,0);
function setData(imageData){
//從imageData對象中取得粒子,并存儲到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);
//新建一個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個粒子中的2336個
粒子篩選
當(dāng)粒子完全寫入時,與canvas復(fù)制粘貼的效果相同。而當(dāng)粒子有所篩選時,則會出現(xiàn)一些奇妙的效果
【按序篩選】
由于取得粒子時,使用的是寬度值*高度值的雙重循環(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);
//寫入drawing2中
cxt2.putImageData(setData(imageData,1),0,0);
function setData(imageData,n){
//從imageData對象中取得粒子,并存儲到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);
}
}
}
//新建一個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ī)篩選。 通過雙重循環(huán)得到的粒子的位置信息,放到dots數(shù)組中。通過splice()方法進(jìn)行篩選,將篩選后的位置信息放到新建的newDots數(shù)組中,然后再使用createImageData(),新建一個圖像數(shù)據(jù)對象并返回
<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);
//寫入drawing2中
cxt2.putImageData(setData(imageData,1),0,0);
function setData(imageData,n,m){
//從imageData對象中取得粒子,并存儲到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個到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;
}
//新建一個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>
像素顯字
下面來使用粒子篩選來實(shí)現(xiàn)一個像素顯字的效果。像素顯字即從不清晰的效果逐步過渡到完全顯示
【按序像素顯字】
按序像素顯字的實(shí)現(xiàn)原理非常簡單,比如,共有2000個粒子,共10個程度的過渡效果。則使用10個數(shù)組,分別保存200,400,600,800,100,1200,1400,1600,1800和2000個粒子。然后使用定時器將其逐步顯示出來即可
<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn">開始顯字</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);
//寫入drawing1中
cxt.putImageData(imageDataArr[index++],0,0);
//迭代函數(shù)
showData();
if(index == 10){
index = 0;
clearTimeout(oTimer);
}
},100);
}
function setData(imageData,n,m){
//從imageData對象中取得粒子,并存儲到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個到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;
}
//新建一個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ī)像素顯字的原理類似,保存多個不同數(shù)量的隨機(jī)像素的數(shù)組即可
<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn">開始顯字</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);
//寫入drawing1中
cxt.putImageData(imageDataArr[index++],0,0);
//迭代函數(shù)
showData();
if(index == 10){
clearTimeout(oTimer);
index = 0;
}
},100);
}
function setData(imageData,n,m){
//從imageData對象中取得粒子,并存儲到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個到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)));
}
//新建一個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>
粒子動畫
粒子動畫并不是粒子在做動畫,而是通過getImageData()方法獲得粒子的隨機(jī)坐標(biāo)和最終坐標(biāo)后,通過fillRect()方法繪制的小方塊在做運(yùn)動。使用定時器,不斷的繪制坐標(biāo)變化的小方塊,以此來產(chǎn)生運(yùn)動的效果
【隨機(jī)位置】
<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn1">開始顯字</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對象中取得粒子,并存儲到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個到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對象中取得粒子,并存儲到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個到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í)現(xiàn)原理很簡單。鼠標(biāo)移動時,通過isPointInPath(x,y)方法檢測,有哪些粒子處于當(dāng)前指針范圍內(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對象中取得粒子,并存儲到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個到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)粒子距離鼠標(biāo)指針小于10時,則進(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)擊時,以鼠標(biāo)指針為圓心的一定范圍內(nèi)的粒子需要移動到該范圍以外。一段時間后,粒子回到原始位置
實(shí)現(xiàn)原理并不復(fù)雜,使用isPointInPath(x,y)方法即可,如果粒子處于當(dāng)前路徑中,則沿著鼠標(biāo)指針與粒子坐標(biāo)組成的直線方向,移動到路徑的邊緣
<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對象中取得粒子,并存儲到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個到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];
}
}
//寫入canvas中
cxt.putImageData(oNewImage,0,0);
//設(shè)置鼠標(biāo)檢測半徑為r
var r = 20;
//鼠標(biāo)移動時,當(dāng)粒子距離鼠標(biāo)指針小于20時,則進(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í)例
下面將上面的效果制作為一個可編輯的綜合實(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)移到文字上時,文字顏色變紅;</span>
<span>2、鼠標(biāo)在文字上點(diǎn)擊時,粒子遠(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對象中取得粒子,并存儲到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個到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];
}
}
//寫入canvas中
cxt.putImageData(oNewImage,0,0);
}
//顯示粒子
function showData(arr,oTimer,index,n){
oTimer = setTimeout(function(){
cxt.clearRect(0,0,W,H);
//寫入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)移動
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)建詳解就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
js獲取GridView中行數(shù)據(jù)的兩種方法 分享
這篇文章介紹了js獲取GridView中行數(shù)據(jù)的方法,有需要的朋友可以參考一下2013-07-07
《javascript設(shè)計(jì)模式》學(xué)習(xí)筆記三:Javascript面向?qū)ο蟪绦蛟O(shè)計(jì)單例模式原理與實(shí)現(xiàn)方法分析
這篇文章主要介紹了Javascript面向?qū)ο蟪绦蛟O(shè)計(jì)單例模式原理與實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了《javascript設(shè)計(jì)模式》中Javascript面向?qū)ο髥卫J较嚓P(guān)概念、原理、用法及操作注意事項(xiàng),需要的朋友可以參考下2020-04-04
微信小程序五子棋游戲AI實(shí)現(xiàn)方法【附demo源碼下載】
這篇文章主要介紹了微信小程序五子棋游戲AI實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了五子棋游戲中人機(jī)對戰(zhàn)的AI原理及相關(guān)實(shí)現(xiàn)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2019-02-02
iframe 自適應(yīng)高度[在IE6 IE7 FF下測試通過]
有時候我們需要讓我們的iframe自動適應(yīng)高度,但多瀏覽器兼容性不好,大家不妨試下這個。2009-04-04
Javascript柯里化實(shí)現(xiàn)原理及作用解析
這篇文章主要介紹了Javascript柯里化實(shí)現(xiàn)原理及作用解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-10-10

