打造高大上的Canvas粒子动画,canvas粒子构建

2019-11-04 11:26 来源:未知

制作高大上的Canvas粒子动漫

2016/08/22 · HTML5 · 5 评论 · Canvas

原稿出处: 腾讯ISUX   

第一来看下大家筹算要做的粒子动漫效果是何许的~

是这样:

图片 1

仍然为如此:

图片 2

竟然是这样:

图片 3

很酷炫!

那什么样去落到实处雷同下面的粒子动漫以致依照本人的喜好去做越来越多别的轨迹的卡通片呢~请看上边详细的执教。

依照canvas粒子系统的创设详整,canvas粒子塑造

前面的话

本文将从最宗旨的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个属性:widthheightdata

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()方法,则该情势中的U讴歌ZDXL不能跨域

【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>

【粒子写入】

对于截然写入来讲,相当于只是简短的复制粘贴,如若要对各类像素点举办精细地调控,则须要接纳粒子写入。canvas第11中学设有着多量的空白区域,唯有'大火柴'那七个字的区域是立竿见影的。于是,能够依据图像数据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只行使了canvas第11中学40000个粒子中的23三十几个

粒子筛选

当粒子完全写入时,与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>

像素显字

上边来行使粒子筛选来贯彻一个像素显字的法力。像素显字即未有清晰的机能日趋对接到完全显示

【按序像素显字】

按序像素显字的完毕原理特别轻松,比方,共有二〇〇〇个粒子,共12个档案的次序的连通效果。则利用十个数组,分别保存200,400,600,800,100,1200,1400,1600,1800和二〇〇二个粒子。然后利用机械漏刻将其稳步显示出来就能够

<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">
 粒子设置:
 <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">
 粒子效果:
 <button id="btn1">按序显字</button>
 <button id="btn2">随机显字</button> 
 <button id="btn3">混乱聚合</button>
 <button id="btn4">重新混乱</button>
</div>
<div>
 鼠标效果:
 1、鼠标移到文字上时,文字颜色变红;
 2、鼠标在文字上点击时,粒子远离鼠标指针
</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粒子系统的塑造详细明白正是小编分享给大家的全体内容了,希望能给我们一个参阅,也可望我们多多关照帮客之家。

前边的话 本文将从最基本的imageData对象的理论知识说开去,详细介绍canvas粒子系统的塑造...

才具接受

因为粒子数量过多,何况事关到图像像素处理,所以这里运用Canvas是不二增选。

 

留意,以下演示的代码只是关键代码,入眼在于解决思路。

黄金时代、绘制粒子概况图

先是要在canvas画布上制图三个由粒子构成的概况图,记录下每二个粒子的坐标,那样才具有持续的动漫。

1. 创办叁个<canvas>成分,并获取Canvas画布渲染上下文

图片 4

< canvas>是贰个双标签成分,通过width和height的值来安装画布的大小。至于ctx(画布渲染上下文卡塔 尔(英语:State of Qatar),能够知晓为画布上的画笔,大家得以由此幅画笔在画布上随机的绘图图案。假设浏览器不接济canvas会直接突显<canvas>标签中间本人设定的文字。当然<canvas>标签中间也得以是一张当不协理canvas时必要替换展现的图形。

 

2. 使用canvas的图像操作API绘制图像

制图图像的第大器晚成API及参数表达:

图片 5

引用MDN上的一张图会比较明晰的看来种种参数的功力:

图片 6

drawImage就是把叁个image对象可能canvas上(以至是video对象上的的每大器晚成帧卡塔 尔(英语:State of Qatar)钦命地点和尺寸的图像绘制到当前的画布上。而在大家的急需中,是要把方方面面图像绘制到画布中。

图片 7

对应浏览器看见的效果:

图片 8

 

3. 收获图像的像素音信,并依靠像素音讯重新绘制出粒子效果轮廓图

canvas有三个叫getImageData的接口,通过该接口能够博得到画布上钦赐地点的全体像素的数码:

图片 9

把收获的imageData输出到调整台能够见见,imageData包罗六天性子:

图片 10

里面,width、height是读取图像像素音讯完整区域的宽度和可观,data是一个Uint8ClampedArray类型的生龙活虎维数组,满含了全副图片区域里种种像素点的HavalGBA的整型数据。这里不可不要通晓那一个数组所保存像素消息的排序法则,请看下图描述的data数组:

图片 11

每一个色值侵占data数组索引的叁个地点,贰个像素有个4个值(Escort、G、B、A卡塔尔国占有数组的4个目录地方。根据数列法规能够驾驭,要收获第n个地点(n从1初步卡塔尔国的奥德赛、G、B像素信息就是:LANDn = (n-1)*4 ,Gn = (n-1)*4+1 ,Bn = (n-1)*4+2  ,so easy~  当然,实际上海图书馆疑似三个席卷image.height行,image.width列像素的矩形实际不是独自的黄金年代行到结束的,那几个n值在矩形中要总括下:

图片 12

是因为叁个像素是包含4个索引值(rgba卡塔 尔(阿拉伯语:قطر‎的,所以获得图像中第i行第j列的哈弗、G、B、A像素消息便是Rij = [(j-1)*imageData.width + (i-1)]*4 ,Gij = [(j-1)*imageData.width + (i-1)]*4 + 1,Bij = [(j-1)*imageData.width + (i-1)]*4 + 2,Aij = [(j-1)*imageData.width + (i-1)]*4 + 3 。种种像素值都足以得到了!

接下去将在把图像的粒子化轮廓图画出来了。那么,怎么办这几个轮廓图啊,大家先读取每一个像素的音信(用到上面的总计公式),要是这些像素的色值相符要求,就保存起来,用于绘制在画布上。别的,既然是做成粒子的意义,大家只供给把像素粒子保存黄金时代部分,体现在画布上。

具体做法是,设定每大器晚成行和每一列要突显的粒子数,分别是cols和rows,三个粒子代表三个单元格,那么各种单元格的的宽高便是imageWidth/cols和imageHeight/rows,然后循环的推断每一种单元格的第贰个像素是不是满足像素值的尺度,假若满意了,就把这几个单元格的坐标保存到数组里,用作后续绘制图案用。

重要参谋代码:

图片 13

用全体代码做出的demo及效果:

图片 14

于今,粒子概略图曾经创造完了。

 

二、制作粒子动漫

创设粒子动画分二种:

朝气蓬勃种是粒子漂浮类,这种比较容易,只须要自由的改造各样粒子的地点值,然后直接履行setInterval或然requestAnimationFrame重摄影布即可,具体的固守因人喜好而去设定,就不具体解说了,做了个简易的粒子漂浮的例子。

另意气风发种是粒子的轨道动漫,这些相对复杂一些。这里要介绍的是各类粒子根据钦赐的轨迹在内定的光阴内做位移,最后汇集成钦定图案的动漫片效果(也正是随笔大器晚成开头的动作效果卡塔 尔(英语:State of Qatar),要做成那类动漫效果要求缓和多个难题:三个是动漫片轨迹,别的七个是各样粒子奉行动漫的机缘。

粒子动漫轨迹

动漫片位移的轨道,最广大的正是单位时间内部管理体制修改变一定的位移值,进而实现动漫效果。但要做到酷炫的职能注重这种平淡固定的移动肯定是特别的。所以位移可以依附缓动函数去达成单位时间内转移不相近的位移值,进而达到特别的效应。

塑造缓动作效果果有三种艺术:

生龙活虎种是自个儿设定好调整点,然后经过贝塞尔曲线公式来总括各类单位时间的坐标值。

引用了wikipedia当中的图:

图片 15图片 16

地方八个图都以在绘制一条特定曲线,能够看到一回曲线供给叁个特定调节点P1,三回曲线须求五个特定调节点P1和P2来鲜明一条曲线,高阶曲线以致供给更加多的调整点来明确曲线轨迹。

求曲线的公式是依附德卡斯特Rio算法算算得来的,直接上公式。

三次曲线对应的公式:

图片 17

贰次曲线对应的公式:

图片 18

从公式能够看看,只要明确调节点坐标、初步坐标和极端坐标后,就足以鲜明了一条曲线,然后就能够依赖曲线公式求出各类时刻t对应的职位值B(t)。

本来使用这种措施必要协和去制定调节点坐标,计算也比较复杂,完毕起来很麻烦。没事,大家还恐怕有别的方法则定曲线。

 

别的意气风发种格局正是利用已部分缓动函数,没有供给团结拟定调整点,这里推荐著名的Tween算法的缓动函数,用个中八个缓动函数来介绍下参数值,别的缓动函数所传的参数值是肖似的:

图片 19

是或不是感到很熟知?对科学,jquery用的动漫片扩大插件easing.js就是Tween算法的缓动函数。有了那现有的缓动函数,就能够拟定粒子的伊始点、终点(终点正是美术本人的坐标地点卡塔尔以至动漫实践持续时间来做大家要的功效。

要害参谋代码:

图片 20

依据参照他事他说加以考查代码做出贰个效果:

图片 21

哦,动漫效果是有了,但总以为不太对劲儿。。。唔,留心考查一下,是摄影动漫履行太过完整了,没有分明性的颗粒动画效果,那就引出粒子动漫的另一个关键点,粒子试行动漫的机遇。

 

粒子推行动漫的机缘

要让粒子效果相比较明显,那就无法让动漫效果施行太过完整了,要求让图案上每种粒子有两样的时间间隔运转,依据早晚的规律交错的执行动漫。这里的粒子运行间距有三种,大器晚成种是每生龙活虎行粒子执行时间隔断,要让每后生可畏行的粒子运行时间有规律错开;其它黄金时代种是每大器晚成行粒子之间运维时间随意的错开,那样执行的粒子动漫才会有意气风发种档次感和每一种粒子有独立动漫的颗粒感。看下加了粒子运转时间间隔之后的效果对比:

图片 22

比上面不加粒子运转时间间隔的成效大多了。

 

啊,介绍大约就是如此了,假诺地点介绍的办法只怕解决不了难题的话,还应该有办法。。。小编把粒子动漫效果和Tween的缓动函数一同装进了一下。直接配置一下就足以用了。 用法正是创办三个带有id的canvas,设定好宽度和冲天,引进particle.min.js,然后配置一下参数就能够, demo:

图片 23

唯有canvasId、imgUrl、cols、rows是必填的,别的参数都以依附须求自个儿选填。  ( ͡° ͜ʖ ͡°)✧

 

正文部分图片援用来自

  • https://zh.wikipedia.org

    3 赞 12 收藏 5 评论

图片 24

TAG标签:
版权声明:本文由金沙澳门唯一官网发布于前端开发,转载请注明出处:打造高大上的Canvas粒子动画,canvas粒子构建