最近想弄一個網頁,把自己學HTML5過程中做的部分DEMO放上去做集合,但是,如果就僅僅做個網頁把所有DEMO一個一個排列又覺得太難看了。就想,既然學了canvas,那就來折騰下瀏覽器,做個小小的開場動畫吧。
開場動畫的效果,想了一會,決定用粒子,因為覺得粒子比較好玩。還記得以前我寫的第一篇技術博文,就是講文字圖片粒子化的:文字圖片粒子化 , 那時就僅僅做的是直線運動,順便加了一點3D效果。運動公式很簡單。所以就想這個開場動畫就做的更動感一些吧。
先上DEMO:http://2.axescanvas.sinaapp.com/demoHome/index.html
效果是不是比直線的運動更加動感呢?而且也確實很簡單,別忘了這篇博文的題目,小小滴公式,大大滴樂趣。要做出這樣的效果,用的就僅僅是我們初中。。或者高中時候的物理知識,加速運動,減速運動的公式啦。所以確實是小小滴公式。樓主很喜歡折騰一些酷炫的東西,雖然可能平時工作上用不上,但是,這樂趣確實很讓人著迷啊。而且,做下這些也可以加強一下程式設計的思維能力哈。
廢話不多說,進入主題啦。就簡單的解釋一下原理吧~~~
粒子運動的核心程式碼就這麼一點:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
update:function(time){ this.x += this.vx*time; this.y += this.vy*time; if(!this.globleDown&&this.y>0){ var yc = this.toy - this.y; var xc = this.tox - this.x; this.jl = Math.sqrt(xc*xc+yc*yc); var za = 20; var ax = za*(xc/this.jl), ay = za*(yc/this.jl), vx = (this.vx+ax*time)*0.97, vy = (this.vy+ay*time)*0.97; this.vx = vx; this.vy = vy; }else { var gravity = 9.8; var vy = this.vy+gravity*time; if(this.y>canvas.height){ vy = -vy*0.7; } this.vy = vy; } }, |
粒子總共有兩種狀態,一種是自由落體,一種就是受到吸力。自由落體就不說了。說吸力之前先貼出粒子的屬性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
var Dot = function(x,y,vx,vy,tox,toy,color){ this.x=x; this.y=y; this.vx=vx; this.vy=vy; this.nextox = tox; this.nextoy = toy; this.color = color; this.visible = true; this.globleDown = false; this.setEnd(tox , toy); } setEnd:function(tox , toy){ this.tox = tox; this.toy = toy; var yc = this.toy - this.y; var xc = this.tox - this.x; }, |
x,y就是粒子的位置,vx是粒子水平速度,vy是粒子的垂直速度,nexttox之類知不知道都無所謂,只是暫時儲存變數的。tox,和toy就是粒子的目的地位置。
首先,先給予所有粒子一個目的地,這個目的地下面再會說。也就是要粒子到達的地方,然後再定義一個變數za作為加速度,具體數值的話,就自己多測試下就會有大概引數的了,我設成20,感覺就差不多了。za是粒子和目的地之間連線的加速度,所以,我們通過粒子的位置和目的地的位置,通過簡單的三角函式,就可以把粒子的水平加速度和垂直加速度求出來了,就這段
1 2 |
var ax = za*(xc/this.jl), ay = za*(yc/this.jl), |
有了水平加速度和垂直加速度後,接下來就更簡單了,直接計算水平速度和垂直速度的增量,從而改變水平速度和垂直速度的值
1 2 |
vx = (this.vx+ax*time)*0.97, vy = (this.vy+ay*time)*0.97; |
之所以要乘於0.97是為了模擬能量損耗,粒子才會減速。time是每一幀的時間差
計算出速度後就更新粒子位置就行了。
1 2 |
this.x += this.vx*time; this.y += this.vy*time; |
因為粒子在飛行過程中,與目的地之間的連線方向是不停改變的,所以每一幀都要重新計算粒子的水平加速度和垂直加速度。
運動原理就是如此,是否很簡單呢。
運動原理說完了,再扯一下上面那個動畫的具體實現吧:動畫初始化,在一個離屏canvas上把想要的字或者圖片畫出來,然後再通過getImageData這個方法獲取離屏canvas的畫素。然後用一個迴圈,把離屏canvas中有繪製的區域找出來,因為imageData裡的data值就是一個rgba陣列,所以我們判斷最後一個的值也就是透明度大於128就是有繪製過的區域。然後獲取該區域的xy值,為了防止粒子物件過多導致頁面卡頓,所以我們就限制一下粒子的數量,取畫素的時候x值和y值每次遞增2,從而減少粒子數量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
this.osCanvas = document.createElement("canvas"); var osCtx = this.osCanvas.getContext("2d"); this.osCanvas.width = 1000; this.osCanvas.height = 150; osCtx.textAlign = "center"; osCtx.textBaseline = "middle"; osCtx.font="70px 微軟雅黑,黑體 bold"; osCtx.fillStyle = "#1D181F" osCtx.fillText("WelCome" , this.osCanvas.width/2 , this.osCanvas.height/2-40); osCtx.fillText("To wAxes' HOME" , this.osCanvas.width/2 , this.osCanvas.height/2+40); var bigImageData = osCtx.getImageData(0,0,this.osCanvas.width,this.osCanvas.height); dots = []; for(var x=0;x<bigImageData.width;x+=2){ for(var y=0;y<bigImageData.height;y+=2){ var i = (y*bigImageData.width + x)*4; if(bigImageData.data[i+3]>128){ var dot = new Dot( Math.random()>0.5?Math.random()*20+10:Math.random()*20+canvas.width-40, -Math.random()*canvas.height*2, 0, 0, x+(canvas.width/2-this.osCanvas.width/2), y+(canvas.height/2-this.osCanvas.height/2), "rgba("+bigImageData.data[i]+","+bigImageData.data[i+1]+","+bigImageData.data[i+2]+",1)" ); dot.setEnd(canvas.width/2,canvas.height/2) dots.push(dot); } } } |
通過迴圈獲取到粒子的位置xy值後,把位置賦給粒子,成為粒子的目的地。然後動畫開始,就可以做出文字圖片粒子化的效果了。
下面貼出動畫實現的js程式碼。如果對其他程式碼也有興趣的,可以直接看控制檯哈,沒壓縮的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
var part_1 = (function(w){ var dots = [],DOT_SIZE = 2,cube=null; var Dot = function(x,y,vx,vy,tox,toy,color){ this.x=x; this.y=y; this.vx=vx; this.vy=vy; this.nextox = tox; this.nextoy = toy; this.color = color; this.visible = true; this.globleDown = false; this.setEnd(tox , toy); } Dot.prototype = { paint:function(){ ctx.fillStyle=this.color; ctx.fillRect(this.x-DOT_SIZE/2 , this.y-DOT_SIZE/2 , DOT_SIZE , DOT_SIZE); }, setEnd:function(tox , toy){ this.tox = tox; this.toy = toy; var yc = this.toy - this.y; var xc = this.tox - this.x; // this.initjl = Math.sqrt(xc*xc+yc*yc); }, update:function(time){ this.x += this.vx*time; this.y += this.vy*time; if(!this.globleDown&&this.y>0){ var yc = this.toy - this.y; var xc = this.tox - this.x; this.jl = Math.sqrt(xc*xc+yc*yc); var za = 20; var ax = za*(xc/this.jl), ay = za*(yc/this.jl), vx = (this.vx+ax*time)*0.97, vy = (this.vy+ay*time)*0.97; this.vx = vx; this.vy = vy; // if(Math.abs(this.vx)<1&&Math.abs(this.vy)<1){ // this.y = this.toy // this.x = this.tox // } }else { var gravity = 9.8; var vy = this.vy+gravity*time; if(this.y>canvas.height){ vy = -vy*0.7; } this.vy = vy; } }, loop:function(time){ this.update(time); this.paint(); } } var animate = function(){ this.state = "before" } var ap = animate.prototype; ap.init = function(){ this.osCanvas = document.createElement("canvas"); var osCtx = this.osCanvas.getContext("2d"); this.osCanvas.width = 1000; this.osCanvas.height = 150; osCtx.textAlign = "center"; osCtx.textBaseline = "middle"; osCtx.font="70px 微軟雅黑,黑體 bold"; osCtx.fillStyle = "#1D181F" osCtx.fillText("WelCome" , this.osCanvas.width/2 , this.osCanvas.height/2-40); osCtx.fillText("To wAxes' HOME" , this.osCanvas.width/2 , this.osCanvas.height/2+40); var bigImageData = osCtx.getImageData(0,0,this.osCanvas.width,this.osCanvas.height); dots = []; for(var x=0;x<bigImageData.width;x+=2){ for(var y=0;y<bigImageData.height;y+=2){ var i = (y*bigImageData.width + x)*4; if(bigImageData.data[i+3]>128){ var dot = new Dot( Math.random()>0.5?Math.random()*20+10:Math.random()*20+canvas.width-40, -Math.random()*canvas.height*2, 0, 0, x+(canvas.width/2-this.osCanvas.width/2), y+(canvas.height/2-this.osCanvas.height/2), "rgba("+bigImageData.data[i]+","+bigImageData.data[i+1]+","+bigImageData.data[i+2]+",1)" ); dot.setEnd(canvas.width/2,canvas.height/2) dots.push(dot); } } } console.log(dots.length) } ap.changeState = function(){ var osCtx = this.osCanvas.getContext("2d"); osCtx.clearRect(0,0,this.osCanvas.width,this.osCanvas.height); this.osCanvas.width = 460; this.osCanvas.height = 100; osCtx.fillStyle="#5C5656" osCtx.fillRect(20,20,60,60) drawLogo(this.osCanvas , osCtx); var bigImageData = osCtx.getImageData(0,0,this.osCanvas.width,this.osCanvas.height); var index=0; dots.sort(function(a , b){ return Math.random()-Math.random(); }) for(var x=0;x<bigImageData.width;x+=2){ for(var y=0;y<bigImageData.height;y+=2){ var i = (y*bigImageData.width + x)*4; if(bigImageData.data[i+3]>128){ var d = dots[index]; if(d){ d.setEnd(x+(canvas.width/2-300) , y+50) d.color = "rgba("+bigImageData.data[i]+","+bigImageData.data[i+1]+","+bigImageData.data[i+2]+",1)"; index++ } } } } setTimeout(function(){ var endindex = index; for(var i=0;i<dots.length-endindex;i++){ if(dots[index]){ var d = dots[index]; d.globleDown = true; d.vx = Math.random()*100-50; } index++; } } , 2000) } function endState(){ canvas.width = 600; canvas.height = 100; canvas.style.display="block"; canvas.style.top = "50px"; canvas.style.left = (window.innerWidth-canvas.width)/2+"px"; cube = new Cube(50); cube._initVector(50,50); } function drawLogo(canvas , ctx){ ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.font="65px 微軟雅黑,黑體 bold" ctx.fillStyle="#E06D2F" ctx.fillText("DEMO" , 300 , canvas.height/2) ctx.font="40px 微軟雅黑,黑體 bold" ctx.fillStyle="#405159" ctx.fillText("吖猩的" , 160 , canvas.height/2) ctx.fillText("小窩" , 420 , canvas.height/2) } var num = 0; ap.update = function(time){ time = time/100; if(this.state==="first"||this.state==="before"){ var completeNum = 0; dots.forEach(function(dot){ if(dot.visible) dot.loop(time); if(dot.jl<5){ completeNum++ } }); if(completeNum>=5*dots.length/6){ if(this.state==="before"){ this.state = "first"; dots.forEach(function(dot){ dot.setEnd(dot.nextox , dot.nextoy); }); }else { this.state = "second"; this.changeState(); } } }else if(this.state==="second"){ var completeNum = 0, allnum = 0; dots.forEach(function(dot){ if(dot.visible) dot.loop(time); if(dot.globleDown){ allnum++; if(Math.abs(dot.y-canvas.height)<2){ completeNum++ } } }); if(completeNum===allnum&&allnum!==0){ this.state = "third"; part_2.animate(); endState(); } }else if(this.state==="third"){ cube.update(); drawLogo(canvas , ctx); } } return new animate(); })(window) |