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>
使用鼠标点击canvas中的文字,出现效果
综合实例
下面将上面的效果制作为一个可编辑的综合实例
<!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粒子系统的构建的更多相关文章
- canvas——粒子系统(1)
这个动画在很早之前就见过,当时就没迷住了.最近在学canavs动画,动手实现了一下.代码在这里.展示效果在这里. 这属于粒子系统的一种,粒子系统就是需要管理一堆粒子嘛,动画实现的关键在于,遍历这些粒子 ...
- Salesforce Lightning Builder Flows (Salesforce Lightning 构建Flows)
本文构建Salesforce Lightning Flows 只是一个简单的实现步骤,原文地址如下: https://trailhead.salesforce.com/en/content/learn ...
- 让你忘记 Flash 的15款精彩 HTML5 游戏
HTML5 游戏开发是一个热门的话题,开发人员和设计人员最近经常谈论到.虽然不能迅速取代 Flash 的地位,但是 HTML5 凭借它的开放性和强大的编程能力,取代 Flash 是必然的趋势.你会看到 ...
- 手势解锁自定义View
package com.rxx.view; import java.util.ArrayList; import java.util.List; import java.util.Timer; imp ...
- HTML5的新结构标签
在之前的HTML页面中,大家基本上都是用了Div+CSS的布局方式.而搜索引擎去抓取页面的内容的时候,它只能猜测你的某个Div内的内容是文章内容容器,或者是导航模块的容器,或者是作者介绍的容器等等.也 ...
- 从IT圈“鄙视链”看前端开发有多难?
如今"鄙视链"体现在生活的方方面面,各行各业都有默认一致的鄙视链.IT圈子因为开发语言多样.工程师岗位种类多.技术框架多,也有自己圈子内的鄙视链.按照开发工程师的岗位形成的鄙视链是 ...
- canvas动画——粒子系统(1)
这个动画在很早之前就见过,当时就没迷住了.最近在学canavs动画,动手实现了一下.代码在这里.展示效果在这里. 这属于粒子系统的一种,粒子系统就是需要管理一堆粒子嘛,动画实现的关键在于,遍历这些粒子 ...
- canvas绘图基础及基于粒子系统的雪花飘落
canvas是html中的一个元素,可以通过js操控绘图! 可以绘制各种图形,各种填充样式! 绘制时可以进行旋转,缩放,平移,但并不是很灵活! 有一对比较好用的方法是save restore! sav ...
- 粒子系统(二):Canvas绘制精美图案
准备 IDE:Visual Studio Code Language:JavaScript / ECMAScript 6+ GitHub:Natural2D.JS 本文主要讲述 Particles - ...
随机推荐
- java核心机制
Java中有两种核心机制:Java虚拟机(Java Virtual Machine).垃圾收集机制(Garbage collection) 一.核心机制之Java虚拟机 ① Java虚拟机可以理解成一 ...
- Azkaban使用简单笔记
官方文档:http://azkaban.github.io/ Azkaban主要的组成:1. 关系型数据库--MySQL2. AzkabanWebServer3. AzkabanExcutorServ ...
- nginx解决方案
nginx解决方案 1.方案场景 现有多台应用服务器,要实现所有的访问先访问到反向代理服务器上再转内部对应的应用. 反向代理(Reverse Proxy)方式是指以代理服务器来接受internet上的 ...
- ES6中的箭头函数
关于函数表达式中的this:自动引用正在调用当前方法的.前的对象1.obj.fun()中的this fun中的this -> obj2.new Fun() Fun中的this -> 正在创 ...
- 图像处理与matlab实例之图像平滑(一)
一.何为图像噪声?噪声是妨碍人的感觉器官所接受信源信息理解的因素,是不可预测只能用概率统计方法认识的随机误差. 举个例子: 从这个图中,我们可以观察到噪声的特点:1>位置随机 2>大小不规 ...
- 第2章 rsync算法原理和工作流程分析
本文通过示例详细分析rsync算法原理和rsync的工作流程,是对rsync官方技术报告和官方推荐文章的解释. 以下是本文的姊妹篇: 1.rsync(一):基本命令和用法 2.rsync(二):ino ...
- JavaScript Base64加解密
Base64加密算法是网络上最常见的用于传输8Bit字节代码的编码方式之一,大家可以查看RFC2045-RFC2049,上面有MIME的详细规范.Base64编码可用于在HTTP环境下传递较长的标识信 ...
- 关于解决mysql数据库乱码的问题
最近在开发的过程中频繁的使用到了mysql'这款数据库,mysql的中文乱码问题一直让人头疼.以前遇到过几次,但是都一不小心就解决了,这次终于明白到底是怎么回事了.可能我下面手的这种解决方案只适合于我 ...
- 【附答案】Java 大数据方向面试题,你会几个?
1.Collection 和 Collections的区别. Collections是个java.util下的类,它包含有各种有关集合操作的静态方法. Collection是个java.uti ...
- 使用gitLab 或 github 关联本地仓库
要先在git里面注册自己的邮箱 然后: git commit -m 是为本次提交命名 刷新gitLab 发现更新了