声明:本文为原创文章,如需转载,请注明来源WAxes,谢谢!
看了岑安大大的教程http://www.cnblogs.com/hongru/archive/2012/03/28/2420415.html后,让我见识到了canvas操控像素能力的强大,也就自己试着做了一下。发现如此好玩的效果也正如岑安大大所说的一样,事情没有想象中那么难。
先看个DEMO吧,先从文字下手:文字粒子化
要做出这样的效果,只需要懂的使用canvas的getImgData()就行了。该方法能够复制画布上指定矩形的像素数据,用法很简单:
var imgData=context.getImageData(x,y,width,height);
就酱紫就可以获取到imgData。imgData是获取到的像素信息,具体如下
对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
- R - 红色 (0-255)
- G - 绿色 (0-255)
- B - 蓝色 (0-255)
- A - alpha 通道 (0-255; 0 是透明的,255 是完全可见的)
只要是有前端编程经验的,都肯定知道rgba了,而获取到的imgData就是一个存放着制定矩形中所有像素数组的数组,第一个像素的R是imgData[0],G是imgData[1],B是imgData[2],A则是imgData[3],第二个像素的R是imgData[4],G是imgData[5],B是imgData[6],A则是imgData[7]。。。以此类推。然后,既然我们已经获取到了所有像素里的rgba参数了,我们也就可以很简单的对这些参数进行更改,然后再将更改后的imgData通过putImageData()方法贴到画布上。像素处理完毕。就是如此简单。
知道如何获取像素数据后,接下来就可以开搞了~~~关键代码就下面这些:
function getimgData(text){
drawText(text);
var imgData = context.getImageData(0,0,canvas.width , canvas.height);
context.clearRect(0,0,canvas.width , canvas.height);
var dots = [];
for(var x=0;x<imgData.width;x+=6){
for(var y=0;y<imgData.height;y+=6){
var i = (y*imgData.width + x)*4;
if(imgData.data[i+3] >= 128){
var dot = new Dot(x-3 , y-3 , 0 , 3);
dots.push(dot);
}
}
}
return dots;
}
获取到imgData后,通过两个循环,获得透明度大于128的,也就是有颜色的像素,然后实例化一个粒子对象,存入一个粒子数组中保存,以下是粒子对象代码:
var Dot = function(centerX , centerY , centerZ , radius){
this.dx = centerX; //保存原来的位置
this.dy = centerY;
this.dz = centerZ;
this.tx = 0; //保存粒子聚合后又飞散开的位置
this.ty = 0;
this.tz = 0;
this.z = centerZ;
this.x = centerX;
this.y = centerY;
this.radius = radius;
}
Dot.prototype = {
paint:function(){
context.save();
context.beginPath();
var scale = focallength/(focallength + this.z);
context.arc(canvas.width/2 + (this.x-canvas.width/2)*scale , canvas.height/2 + (this.y-canvas.height/2) * scale, this.radius*scale , 0 , 2*Math.PI);
context.fillStyle = "rgba(50,50,50,"+ scale +")";
context.fill()
context.restore();
}
}
为了让小圆扩散有3D的空间感,所以还引入了Z轴,也就是把3维平面化成二维,具体我就不多说了。可以直接戳http://www.cnblogs.com/hongru/archive/2011/09/12/2174187.html 我就是看着岑安大大的教程学的。3D效果也做好后,就做动画,随机出一个坐标,让粒子先呆在那个坐标,然后通过保存的位置再聚合,然后再随机出坐标再分散,就是这个动画的原理了。
下面贴出所有代码:
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> 5 <style> 6 #cas{ 7 display: block; 8 border:1px solid; 9 margin:auto; 10 } 11 </style> 12 <script> 13 window.onload = function(){ 14 canvas = document.getElementById("cas"); 15 context = canvas.getContext('2d'); 16 focallength = 250; 17 18 var dots = getimgData(document.getElementById('name').value);; 19 var pause = false; 20 initAnimate(); 21 function initAnimate(){ 22 dots.forEach(function(){ 23 this.x = Math.random()*canvas.width; 24 this.y = Math.random()*canvas.height; 25 this.z = Math.random()*focallength*2 - focallength; 26 27 this.tx = Math.random()*canvas.width; 28 this.ty = Math.random()*canvas.height; 29 this.tz = Math.random()*focallength*2 - focallength; 30 this.paint(); 31 }); 32 animate(); 33 } 34 35 //计算帧速率 36 var lastTime; 37 var derection = true; 38 function animate(){ 39 animateRunning = true; 40 var thisTime = +new Date(); 41 context.clearRect(0,0,canvas.width , canvas.height); 42 dots.forEach(function(){ 43 var dot = this; 44 if(derection){ 45 if (Math.abs(dot.dx - dot.x) < 0.1 && Math.abs(dot.dy - dot.y) < 0.1 && Math.abs(dot.dz - dot.z)<0.1) { 46 dot.x = dot.dx; 47 dot.y = dot.dy; 48 dot.z = dot.dz; 49 if(thisTime - lastTime > 300) derection = false; 50 } else { 51 dot.x = dot.x + (dot.dx - dot.x) * 0.1; 52 dot.y = dot.y + (dot.dy - dot.y) * 0.1; 53 dot.z = dot.z + (dot.dz - dot.z) * 0.1; 54 lastTime = +new Date() 55 } 56 } 57 else { 58 if (Math.abs(dot.tx - dot.x) < 0.1 && Math.abs(dot.ty - dot.y) < 0.1 && Math.abs(dot.tz - dot.z)<0.1) { 59 dot.x = dot.tx; 60 dot.y = dot.ty; 61 dot.z = dot.tz; 62 pause = true; 63 } else { 64 dot.x = dot.x + (dot.tx - dot.x) * 0.1; 65 dot.y = dot.y + (dot.ty - dot.y) * 0.1; 66 dot.z = dot.z + (dot.tz - dot.z) * 0.1; 67 pause = false; 68 } 69 } 70 dot.paint(); 71 }); 72 if(!pause) { 73 if("requestAnimationFrame" in window){ 74 requestAnimationFrame(animate); 75 } 76 else if("webkitRequestAnimationFrame" in window){ 77 webkitRequestAnimationFrame(animate); 78 } 79 else if("msRequestAnimationFrame" in window){ 80 msRequestAnimationFrame(animate); 81 } 82 else if("mozRequestAnimationFrame" in window){ 83 mozRequestAnimationFrame(animate); 84 } 85 } 86 } 87 88 document.getElementById('startBtn').onclick = function(){ 89 if(!pause) return; 90 dots = getimgData(document.getElementById('name').value); 91 derection=true; 92 pause = false; 93 initAnimate(); 94 } 95 } 96 97 Array.prototype.forEach = function(callback){ 98 for(var i=0;i<this.length;i++){ 99 callback.call(this[i]); 100 } 101 } 102 103 function getimgData(text){ 104 drawText(text); 105 var imgData = context.getImageData(0,0,canvas.width , canvas.height); 106 context.clearRect(0,0,canvas.width , canvas.height); 107 var dots = []; 108 for(var x=0;x<imgData.width;x+=6){ 109 for(var y=0;y<imgData.height;y+=6){ 110 var i = (y*imgData.width + x)*4; 111 if(imgData.data[i] >= 128){ 112 var dot = new Dot(x-3 , y-3 , 0 , 3); 113 dots.push(dot); 114 } 115 } 116 } 117 return dots; 118 } 119 120 function drawText(text){ 121 context.save() 122 context.font = "200px 微软雅黑 bold"; 123 context.fillStyle = "rgba(168,168,168,1)"; 124 context.textAlign = "center"; 125 context.textBaseline = "middle"; 126 context.fillText(text , canvas.width/2 , canvas.height/2); 127 context.restore(); 128 } 129 130 131 var Dot = function(centerX , centerY , centerZ , radius){ 132 this.dx = centerX; 133 this.dy = centerY; 134 this.dz = centerZ; 135 this.tx = 0; 136 this.ty = 0; 137 this.tz = 0; 138 this.z = centerZ; 139 this.x = centerX; 140 this.y = centerY; 141 this.radius = radius; 142 } 143 144 Dot.prototype = { 145 paint:function(){ 146 context.save(); 147 context.beginPath(); 148 var scale = focallength/(focallength + this.z); 149 context.arc(canvas.width/2 + (this.x-canvas.width/2)*scale , canvas.height/2 + (this.y-canvas.height/2) * scale, this.radius*scale , 0 , 2*Math.PI); 150 context.fillStyle = "rgba(50,50,50,"+ scale +")"; 151 context.fill() 152 context.restore(); 153 } 154 } 155 </script> 156 <title>操控字体的数据</title> 157 </head> 158 <body> 159 <div > 160 <canvas id='cas' width="1000" height="500">浏览器不支持canvas</canvas> 161 <div style="width:150px;margin:10px auto"> 162 <input type="text" name="" id="name" style="width:80px;" value="王鸿兴"><button id="startBtn">开始</button> 163 </div> 164 </div> 165 </body> 166 </html>
技术不是很好,代码写的不好请见谅。不过应该不难理解。
知道文字如何粒子化后,图片也就一样的做法了。这里是另一个demo 粒子化Demo1。
源码地址:https://github.com/whxaxes/canvas-test/tree/gh-pages/src/Particle-demo/imgdata
补充一点:过于复杂的图片不适合粒子化,因为对象过多时,对浏览器的造成很大的负荷,动画效果也就会变得很卡很卡了。一般一千个粒子左右比较合适,自己就根据图片复杂度调整粒子的大小,减少粒子数量就行了。