当前位置: 移动技术网 > IT编程>开发语言>JavaScript > 基于canvas粒子系统的构建详解

基于canvas粒子系统的构建详解

2017年12月12日  | 移动技术网IT编程  | 我要评论
前面的话 本文将从最基本的imagedata对象的理论知识说开去,详细介绍canvas粒子系统的构建 imagedata 关于图像数据imagedata共有3个方法,

前面的话

本文将从最基本的imagedata对象的理论知识说开去,详细介绍canvas粒子系统的构建

imagedata

关于图像数据imagedata共有3个方法,包括getimagedata()、putimagedata()、createimagedata()

【getimagedata()】

2d上下文可以通过getimagedata()取得原始图像数据。这个方法接收4个参数:画面区域的x和y坐标以及该区域的像素宽度和高度

例如,要取得左上角坐标为(10,5)、大小为50*50像素的区域的图像数据,可以使用以下代码:

var imagedata = context.getimagedata(10,5,50,50);

返回的对象是imagedata的实例,每个imagedata对象有3个属性:width\height\data

1、width:表示imagedata对角的宽度

2、height:表示imagedata对象的高度

3、data是一个数组,保存着图像中每一个像素的数据。在data数组中,每一个像素用4个元素来保存,分别表示red、green、blue、透明度

[注意]图像中有多少像素,data的长度就等于像素个数乘以4

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

数组中每个元素的值是在0-255之间,能够直接访问到原始图像数据,就能够以各种方式来操作这些数据

[注意]如果要使用getimagedata()获取的canvas中包含drawimage()方法,则该方法中的url不能跨域

【createimagedata()】

createimagedata(width,height)方法创建新的空白imagedata对象。新对象的默认像素值 transparent black,相当于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中的每一个像素点。下面以一个简易实例来说明完全写入与粒子写入

【完全写入】

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('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>

【粒子写入】

对于完全写入而言,相当于只是简单的复制粘贴,如果要对每个像素点进行精细地控制,则需要使用粒子写入。canvas1中存在着大量的空白区域,只有'小火柴'这三个字的区域是有效的。于是,可以根据图像数据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('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数组中
 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只使用了canvas1中40000个粒子中的2336个

粒子筛选

当粒子完全写入时,与canvas复制粘贴的效果相同。而当粒子有所筛选时,则会出现一些奇妙的效果

【按序筛选】

由于取得粒子时,使用的是宽度值*高度值的双重循环,且都以加1的形式递增。如果不是加1,而是加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数组中
 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>

【随机筛选】

除了使用按序筛选,还可以使用随机筛选。 通过双重循环得到的粒子的位置信息,放到dots数组中。通过splice()方法进行筛选,将筛选后的位置信息放到新建的newdots数组中,然后再使用createimagedata(),新建一个图像数据对象并返回

<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数组中
 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>

像素显字

下面来使用粒子筛选来实现一个像素显字的效果。像素显字即从不清晰的效果逐步过渡到完全显示

【按序像素显字】

按序像素显字的实现原理非常简单,比如,共有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('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); 
  //迭代函数  
  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('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); 
  //迭代函数  
  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('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数组中
 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++] = {
   'index':index,
   'x':i,
   'y':j,
   'red':k,
   'randomx':math.random()*w,
   'randomy':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('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数组中
 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++] = {
   'index':index,
   'x':i,
   'y':j,
   'red':k,
   'randomx':math.random()*w,
   'randomy':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('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数组中
 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++] = {
   'index':index,
   'x':i,
   'y':j,
   'red':k,
   'randomx':math.random()*w,
   'randomy':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 = 'red';
  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('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数组中
 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++] = {
   'index':index,
   'x':i,
   'y':j,
   'red':k,
   'randomx':math.random()*w,
   'randomy':math.random()*h,
   'mark':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 = '#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>

综合实例

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

<!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>粒子设置:</span>
 <input type="text" id="textvalue" value="小火柴的蓝色理想"> 
 <button id="btnsettext">文字设置确认</button>
 <button id="btnchoose2">按序筛选</button>
 <button id="btnchoose3">随机筛选</button>
 <button id="btnchoose1">不筛选</button> 
</div>
<div style="margin-bottom:10px">
 <span>粒子效果:</span>
 <button id="btn1">按序显字</button>
 <button id="btn2">随机显字</button> 
 <button id="btn3">混乱聚合</button>
 <button id="btn4">重新混乱</button>
</div>
<div>
 <span>鼠标效果:</span>
 <span>1、鼠标移到文字上时,文字颜色变红;</span>
 <span>2、鼠标在文字上点击时,粒子远离鼠标指针</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数组中
 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++] = {
   '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数组中
 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 = 'red';
  cxt.fillrect(temp.x,temp.y,1,1);
  }  
 }
 cxt.fillstyle = 'black'; 
 } 
 //鼠标点击
 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粒子系统的构建详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持移动技术网。

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网