聲明:本文為原創文章,如需轉載,請注明來源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
補充一點:過於復雜的圖片不適合粒子化,因為對象過多時,對瀏覽器的造成很大的負荷,動畫效果也就會變得很卡很卡了。一般一千個粒子左右比較合適,自己就根據圖片復雜度調整粒子的大小,減少粒子數量就行了。