基于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个属性: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粒子系统的构建详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
下一篇: Vue 滚动行为的具体使用方法