>  기사  >  웹 프론트엔드  >  캔버스 입자 시스템의 구축 방법에 대한 자세한 설명

캔버스 입자 시스템의 구축 방법에 대한 자세한 설명

巴扎黑
巴扎黑원래의
2017-09-01 11:01:411278검색

다음 에디터는 캔버스 파티클 시스템 구성에 대한 자세한 설명을 제공합니다. 편집자님이 꽤 좋다고 생각하셔서 지금 공유하고 모두에게 참고용으로 드리도록 하겠습니다. 에디터 따라가서 살펴볼까요

앞서 말한 내용

이 글은 imageData 객체에 대한 가장 기본적인 이론적 지식부터 시작해 캔버스 파티클 시스템의 구성을 자세히 소개하겠습니다

imageData

이미지 데이터에 대하여 imageData에는 getImageData(), putImageData(), createImageData()

【getImageData()】

2D 컨텍스트의 세 가지 메소드가 있으며 getImageData()를 통해 원본 이미지 데이터를 얻을 수 있습니다. 이 메소드는 화면 영역의 x 및 y 좌표와 영역의 픽셀 너비 및 높이라는 4개의 매개변수를 받습니다. 예를 들어 왼쪽 위 모서리의 좌표(10,5)와 영역의 이미지 데이터를 얻습니다. 50*50 픽셀 크기, 다음 코드를 사용할 수 있습니다:

var imageData = context.getImageData(10,5,50,50);

반환된 개체는 ImageData의 인스턴스입니다. 각 ImageData 개체에는 widthheightdata

1, 너비: 대각선 너비를 나타냅니다. imageData

2, height: imageData 객체를 나타냅니다.

3 데이터의 높이는 이미지의 각 픽셀 데이터를 저장하는 배열입니다. 데이터 배열에서 각 픽셀은 각각 빨간색, 녹색, 파란색, 투명도를 나타내는 4개의 요소로 저장됩니다. [참고] 이미지에 있는 픽셀 수는 데이터의 길이를 픽셀 수에 곱한 것과 같습니다. 4

//第一个像素如下
var data = imageData.data;
var red = data[0];
var green = data[1]; 
var blue = data[2];
var alpha = data[3];

배열의 각 요소의 값은 0~255 사이입니다. 원본 이미지 데이터에 직접 접근할 수 있다면 이 데이터를 다양한 방식으로 조작할 수 있습니다


[참고] getImageData를 사용하려는 경우 ()를 사용하여 캔버스를 얻으려면 drawImage() 메소드가 포함된 경우 이 메소드의 URL은 도메인을 교차할 수 없습니다.

[createImageData()]

createImageData(width,height) 메소드는 새로운 빈 ImageData 객체를 생성합니다. 새 객체의 기본 픽셀 값은 투명한 검정색이며, 이는 rgba(0,0,0,0)

var imgData = context.createImageData(100,100);

【putImageData()】


putImageData() 메소드와 동일합니다. 이미지 데이터를 다시 지정된 ImageData 객체의 캔버스에서 이 메소드에는 다음 매개변수가 있습니다.

imgData:要放回画布的ImageData对象(必须)
x:imageData对象的左上角的x坐标(必须)
y:imageData对象的左上角的y坐标(必须)
dirtyX:在画布上放置图像的水平位置(可选)
dirtyY:在画布上放置图像的垂直位置(可选)
dirtyWidth:在画布上绘制图像所使用的宽度(可选)
dirtyHeight:在画布上绘制图像所使用的高度(可选)

[참고] 매개변수 3~7은 없거나 모두 존재합니다.


context.putImageData(imgData,0,0)

context.putImageData( imgData,0,0, 50,50,200,200);



파티클 라이팅

파티클은 이미지 데이터 imageData의 각 픽셀을 의미합니다. 다음은 전체 쓰기와 입자 쓰기를 설명하기 위한 간단한 예입니다【Full Writing】

'Little Match'라는 텍스트는 200*200의 canvas1에 존재하며, canvas1 전체가 동일한 크기의 canvas2에 이미지 데이터로 쓰여집니다.

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById(&#39;drawing1&#39;);
var drawing2 = document.getElementById(&#39;drawing2&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var cxt2 = drawing2.getContext(&#39;2d&#39;);
 var W = drawing1.width = drawing2.width = 200;
 var H = drawing1.height = drawing2.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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>

【입자 쓰기】


완전한 쓰기를 위해서는 간단한 복사 및 붙여넣기와 같습니다. 각 픽셀을 세밀하게 제어하려면 입자 쓰기를 사용해야 합니다. canvas1에는 공백이 많은데, 'little match'라는 단어가 있는 영역만 유효합니다. 따라서 이미지 데이터 imageData의 투명도를 기준으로 입자를 필터링할 수 있으며 투명도가 0보다 큰 입자만 필터링됩니다.

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById(&#39;drawing1&#39;);
var drawing2 = document.getElementById(&#39;drawing2&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var cxt2 = drawing2.getContext(&#39;2d&#39;);
 var W = drawing1.width = drawing2.width = 200;
 var H = drawing1.height = drawing2.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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数组中
 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数组中
   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>

결과는 동일해 보이지만 canvas2는 40000개의 입자 중 2336개만 사용합니다. in canvas1


Particle Filter

파티클이 완전히 쓰여지면 캔버스 복사 붙여넣기와 같은 효과가 나타납니다. 입자를 필터링하면 멋진 효과가 나타납니다 【순서대로 선별하기】

입자를 얻을 때 너비 값 * 높이 값의 이중 루프가 사용되며 둘 다 1을 더해 증가합니다. 1을 더하는 대신 n을 더하면 순차 필터링 효과를 얻을 수 있습니다

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<p id="con">
 <button>1</button>
 <button>2</button>
 <button>3</button>
 <button>4</button>
 <button>5</button>
</p>
<script>
var oCon = document.getElementById(&#39;con&#39;);
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(&#39;drawing1&#39;);
var drawing2 = document.getElementById(&#39;drawing2&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var cxt2 = drawing2.getContext(&#39;2d&#39;);
 var W = drawing1.width = drawing2.width = 200;
 var H = drawing1.height = drawing2.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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数组中
 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数组中
   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>

【랜덤 필터링】


순차 필터링 외에 랜덤 필터링도 사용할 수 있습니다. 이중 루프를 통해 얻은 입자의 위치 정보는 도트 배열에 배치됩니다. splice() 메소드를 통해 필터링하고 필터링된 위치 정보를 새로 생성된 newDots 배열에 넣은 다음 createImageData()를 사용하여 새 이미지 데이터 객체를 만들고

<canvas id="drawing1" style="border:1px solid black"></canvas>
<canvas id="drawing2" style="border:1px solid black"></canvas>
<p id="con">
 <button>1000</button>
 <button>2000</button>
 <button>3000</button>
 <button>4000</button>
</p>
<script>
var oCon = document.getElementById(&#39;con&#39;);
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(&#39;drawing1&#39;);
var drawing2 = document.getElementById(&#39;drawing2&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var cxt2 = drawing2.getContext(&#39;2d&#39;);
 var W = drawing1.width = drawing2.width = 200;
 var H = drawing1.height = drawing2.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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数组中
 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数组中
   dots.push(k);
  }
  }
 } 
 //筛选粒子,仅保存m个到newDots数组中。如果不传入m,则不进行筛选
 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>


pixel display

를 반환합니다. Let's 입자 필터링을 사용하여 픽셀에 텍스트를 표시하는 효과를 얻습니다. 픽셀 디스플레이는 불분명한 효과에서 전체 디스플레이로 점진적으로 전환되는 것을 의미합니다.[순차 픽셀 디스플레이]

순차 픽셀 디스플레이의 구현 원리는 매우 간단합니다. 예를 들어 총 2000개의 입자와 총 10단계의 전환 효과가 있습니다. . 그런 다음 10개의 어레이를 사용하여 각각 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(&#39;drawing1&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var W = drawing1.width = 200;
 var H = drawing1.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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); 
  //迭代函数  
  showData();  
  if(index == 10){
    index = 0;
  clearTimeout(oTimer);
  }  

 },100);  
 } 
 function setData(imageData,n,m){
 //从imageData对象中取得粒子,并存储到dots数组中
 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数组中
   dots.push(k);
  }
  }
 } 
 //筛选粒子,仅保存m个到newDots数组中。如果不传入m,则不进行筛选
 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>

【무작위 픽셀 표시】


무작위 픽셀 표시의 원리는 유사합니다. 서로 다른 숫자로 여러 개의 무작위 픽셀 배열을 저장하면 됩니다

<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn">开始显字</button>
<script>
var drawing1 = document.getElementById(&#39;drawing1&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var W = drawing1.width = 200;
 var H = drawing1.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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); 
  //迭代函数  
  showData();  
  if(index == 10){
  clearTimeout(oTimer);
  index = 0;
  }  
 },100);  
 } 
 function setData(imageData,n,m){
 //从imageData对象中取得粒子,并存储到dots数组中
 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数组中
   dots.push(k);
  }
  }
 } 
 //筛选粒子,仅保存dots.length/m个到newDots数组中
 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()方法获得粒子的随机坐标和最终坐标后,通过fillRect()方法绘制的小方块在做运动。使用定时器,不断的绘制坐标变化的小方块,以此来产生运动的效果

【随机位置】


<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn1">开始显字</button>
<button id="btn2">重新混乱</button>
<script>
var drawing1 = document.getElementById(&#39;drawing1&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var W = drawing1.width = 200;
 var H = drawing1.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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数组中
 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数组中
   dots.push(k);
   dots[index++] = {
   &#39;index&#39;:index,
   &#39;x&#39;:i,
   &#39;y&#39;:j,
   &#39;red&#39;:k,
   &#39;randomX&#39;:Math.random()*W,
   &#39;randomY&#39;:Math.random()*H,
   }
  }
  }
 } 
 //筛选粒子,仅保存dots.length/m个到newDots数组中
 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;
 }
 //获得粒子数组
 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>

【飘入效果】 

飘入效果与随机显字的原理相似,不再赘述


<canvas id="drawing1" style="border:1px solid black"></canvas>
<button id="btn1">左上角飘入</button>
<script>
var drawing1 = document.getElementById(&#39;drawing1&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var W = drawing1.width = 200;
 var H = drawing1.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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数组中
 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数组中
   dots.push(k);
   dots[index++] = {
   &#39;index&#39;:index,
   &#39;x&#39;:i,
   &#39;y&#39;:j,
   &#39;red&#39;:k,
   &#39;randomX&#39;:Math.random()*W,
   &#39;randomY&#39;:Math.random()*H,
   }
  }
  }
 } 
 //筛选粒子,仅保存dots.length/m个到newDots数组中
 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;
 }
 //获得粒子数组
 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>

鼠标交互

一般地,粒子的鼠标交互都与isPointInPath(x,y)方法有关

【移入变色】

当鼠标接近粒子时,该粒子变红。实现原理很简单。鼠标移动时,通过isPointInPath(x,y)方法检测,有哪些粒子处于当前指针范围内。如果处于,绘制1像素的红色矩形即可


<canvas id="drawing1" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById(&#39;drawing1&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var W = drawing1.width = 200;
 var H = drawing1.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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数组中
 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数组中
   dots.push(k);
   dots[index++] = {
   &#39;index&#39;:index,
   &#39;x&#39;:i,
   &#39;y&#39;:j,
   &#39;red&#39;:k,
   &#39;randomX&#39;:Math.random()*W,
   &#39;randomY&#39;:Math.random()*H,
   }
  }
  }
 } 
 //筛选粒子,仅保存dots.length/m个到newDots数组中
 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;
 }
 //获得粒子数组
 var dataArr = setData(imageData,1,1); 
 //鼠标移动时,当粒子距离鼠标指针小于10时,则进行相关操作
 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 = &#39;red&#39;;
  cxt.fillRect(temp.x,temp.y,1,1);
  }  
 } 
 }
}
</script>

【远离鼠标】

鼠标点击时,以鼠标指针为圆心的一定范围内的粒子需要移动到该范围以外。一段时间后,粒子回到原始位置

实现原理并不复杂,使用isPointInPath(x,y)方法即可,如果粒子处于当前路径中,则沿着鼠标指针与粒子坐标组成的直线方向,移动到路径的边缘


<canvas id="drawing1" style="border:1px solid black"></canvas>
<script>
var drawing1 = document.getElementById(&#39;drawing1&#39;);
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 var W = drawing1.width = 200;
 var H = drawing1.height = 200;
 var str = &#39;小火柴&#39;;
 cxt.textBaseline = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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数组中
 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数组中
   dots.push(k);
   dots[index++] = {
   &#39;index&#39;:index,
   &#39;x&#39;:i,
   &#39;y&#39;:j,
   &#39;red&#39;:k,
   &#39;randomX&#39;:Math.random()*W,
   &#39;randomY&#39;:Math.random()*H,
   &#39;mark&#39;:false
   }
  }
  }
 } 
 //筛选粒子,仅保存dots.length/m个到newDots数组中
 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;
 }
 //获得粒子数组
 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);
 //设置鼠标检测半径为r
 var r = 20;
 //鼠标移动时,当粒子距离鼠标指针小于20时,则进行相关操作
 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 = &#39;#fff&#39;;
  cxt.fillRect(temp.x,temp.y,1,1);
  cxt.fillStyle = &#39;#000&#39;;
  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>

综合实例

下面将上面的效果制作为一个可编辑的综合实例


<!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>
<p style="margin-bottom:10px">
 <span>粒子设置:</span>
 <input type="text" id="textValue" value="小火柴的蓝色理想"> 
 <button id="btnSetText">文字设置确认</button>
 <button id="btnchoose2">按序筛选</button>
 <button id="btnchoose3">随机筛选</button>
 <button id="btnchoose1">不筛选</button> 
</p>
<p style="margin-bottom:10px">
 <span>粒子效果:</span>
 <button id="btn1">按序显字</button>
 <button id="btn2">随机显字</button> 
 <button id="btn3">混乱聚合</button>
 <button id="btn4">重新混乱</button>
</p>
<p>
 <span>鼠标效果:</span>
 <span>1、鼠标移到文字上时,文字颜色变红;</span>
 <span>2、鼠标在文字上点击时,粒子远离鼠标指针</span>
</p>
<script>
if(drawing1.getContext){
 var cxt = drawing1.getContext(&#39;2d&#39;);
 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 = &#39;top&#39;;
 var sh = 60;
 cxt.font = sh + &#39;px 宋体&#39;
 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(&#39;小火柴&#39;);
 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数组中
 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数组中
   dots.push(k);
   dots[index++] = {
   &#39;index&#39;:index,
   &#39;x&#39;:i,
   &#39;y&#39;:j,
   &#39;red&#39;:k,
   &#39;green&#39;:k+1,
   &#39;blue&#39;:k+2,
   &#39;randomX&#39;:Math.random()*W,
   &#39;randomY&#39;:Math.random()*H,
   &#39;mark&#39;:false
   }
  }
  }
 } 
 //筛选粒子,仅保存dots.length/m个到newDots数组中
 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{
  //迭代函数  
  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);
 } 
 //鼠标移动
 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 = &#39;red&#39;;
  cxt.fillRect(temp.x,temp.y,1,1);
  }  
 }
 cxt.fillStyle = &#39;black&#39;; 
 } 
 //鼠标点击
 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 = &#39;#fff&#39;;
  cxt.fillRect(temp.x,temp.y,1,1);
  cxt.fillStyle = &#39;#f00&#39;;
  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>

위 내용은 캔버스 입자 시스템의 구축 방법에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.