基于canvas粒子系統(tǒng)的構(gòu)建詳解
前面的話
本文將從最基本的imageData對(duì)象的理論知識(shí)說(shuō)開去,詳細(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ù):畫面區(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ì)象放回畫布上,該方法共有以下參數(shù)
imgData:要放回畫布的ImageData對(duì)象(必須) x:imageData對(duì)象的左上角的x坐標(biāo)(必須) y:imageData對(duì)象的左上角的y坐標(biāo)(必須) dirtyX:在畫布上放置圖像的水平位置(可選) dirtyY:在畫布上放置圖像的垂直位置(可選) dirtyWidth:在畫布上繪制圖像所使用的寬度(可選) dirtyHeight:在畫布上繪制圖像所使用的高度(可選)
[注意]參數(shù)3到7要么都沒(méi)有,要么都存在
context.putImageData(imgData,0,0);
context.putImageData(imgData,0,0,50,50,200,200);
粒子寫入
粒子,指圖像數(shù)據(jù)imageData中的每一個(gè)像素點(diǎn)。下面以一個(gè)簡(jiǎn)易實(shí)例來(lái)說(shuō)明完全寫入與粒子寫入
【完全寫入】
200*200的canvas1中存在文字'小火柴',并將canvas1整個(gè)作為圖像數(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>
【粒子寫入】
對(duì)于完全寫入而言,相當(dāng)于只是簡(jiǎn)單的復(fù)制粘貼,如果要對(duì)每個(gè)像素點(diǎn)進(jìn)行精細(xì)地控制,則需要使用粒子寫入。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);
//寫入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)粒子完全寫入時(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);
//寫入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);
//寫入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">開始顯字</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對(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ī)像素顯字的原理類似,保存多個(gè)不同數(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對(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)畫
粒子動(dòng)畫并不是粒子在做動(dòng)畫,而是通過(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">開始顯字</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];
}
}
//寫入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];
}
}
//寫入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)移動(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è)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Javascript模仿淘寶信用評(píng)價(jià)實(shí)例(附源碼)
這篇文章主要介紹了Javascript模仿淘寶信用評(píng)價(jià)功能實(shí)現(xiàn)方法,以完整實(shí)例形式分析了JavaScript響應(yīng)鼠標(biāo)事件動(dòng)態(tài)改變頁(yè)面元素的相關(guān)技巧,并附帶了完整的實(shí)例代碼供讀者下載參考,需要的朋友可以參考下2015-11-11
JavaScript中的Reflect對(duì)象詳解(ES6新特性)
這篇文章主要介紹了JavaScript中的Reflect對(duì)象(ES6新特性)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07
深入探討JavaScript中Class的語(yǔ)法與使用
這篇文章將帶大家深入探討 class 在 JavaScript 中的作用、語(yǔ)法和使用方法,并與 ES5 構(gòu)造函數(shù)進(jìn)行對(duì)比,希望可以幫助大家更好地理解和應(yīng)用類的概念2023-06-06
var?let?const關(guān)鍵字之間的區(qū)別及使用場(chǎng)景示例詳解
這篇文章主要為大家介紹了var?let?const關(guān)鍵字之間的區(qū)別及使用場(chǎng)景示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
javascript常見(jiàn)數(shù)字進(jìn)制轉(zhuǎn)換實(shí)例分析
這篇文章主要介紹了javascript常見(jiàn)數(shù)字進(jìn)制轉(zhuǎn)換,結(jié)合實(shí)例形式分析了JavaScript十進(jìn)制,十六進(jìn)制及二進(jìn)制的相互轉(zhuǎn)換原理與技巧,需要的朋友可以參考下2016-04-04

