不知不覺又好久沒更過博客了,老調新彈一下,之前做的一個小效果,覺得蠻有意思的,也有朋友問是怎么做的,就分享一下,寫個博文吧。
先上demo吧:http://whxaxes.github.io/canvas-test/src//Funny-demo/netparticle/net_1.html
上面這個demo是最早寫的,后來做了點小修改后就用到了自己的網站上當個banner,有興趣的也可以看看效果:http://wanghx.cn/ ,至少同事說還是挺酷炫的。這種效果其實很早之前就有了的,我也是在一個網站上看到類似的效果,發現這個創意不錯,而且難度很小,就花了一個午休的時間寫了一下。
接下來就分析一下怎么實現把。
這種效果一眼看過去,就知道其實就是一堆粒子在進行無序的運動。然后當粒子與粒子之間的距離小於一定值后,就進行連線,並且根據距離的大小來對線條的粗細進行一些更改,就可以做出這種有點像蛛網的感覺了。原理很簡單,直接上代碼:
var dots = []; for (var i = 0; i < 200; i++) { var x = Math.random() * (canvas.width + 2*extendDis) - extendDis; var y = Math.random() * (canvas.height + 2*extendDis) - extendDis; var xa = (Math.random() * 2 - 1)/1.5; var ya = (Math.random() * 2 - 1)/1.5; dots.push({x, y, xa, ya}) }
首先,用一個數組,裝載兩百個分散在canvas各處的粒子對象,並且給每個對象一個隨機的運動趨勢。也就是xa和ya,用於表示垂直和水平的運動趨勢。其實就是一個用於每次循環的時候進行疊加的值。
實例化好兩百個粒子對象后。就可以讓他們開始運動:
dot.x += dot.xa; dot.y += dot.ya; // 遇到邊界將速度反向 dot.xa *= (dot.x > (canvas.width + extendDis) || dot.x < -extendDis) ? -1 : 1; dot.ya *= (dot.y > (canvas.height + extendDis) || dot.y < -extendDis) ? -1 : 1; // 繪制點 ctx.fillStyle = `rgba(${rgb},${rgb},${rgb},1`; ctx.fillRect(dot.x - 0.5, dot.y - 0.5, 1, 1);
運動的邏輯也很簡單,每次給粒子更新新的狀態,其實就是根據此前初始化粒子的時候給予的xa和ya,進行一個累加,就可以形成運動的效果了。
當然,粒子不能往一個方向無限的運動下去,所以我們還需要判斷粒子是否運動到邊界了,如果運動到了邊界,就把運動趨勢進行反轉。也就做出了一種粒子反彈的效果。上面的extendDis其實是我為了讓粒子反彈點在canvas外而定義的一個變量,用於控制粒子跑到離開canvas多遠后才進行反彈。
當然,每次運動完都對粒子進行一個繪制。這一段代碼會放到一個叫move的function里。
就上面的一些代碼,就完成了粒子的初始化,以及運動了。接下來就是畫線了。邏輯也很簡單,就是遍歷,逐個粒子計算距離,當兩個比較的粒子之間的距離小於某個值,就進行畫線。代碼如下:
/** * 逐個對比連線 * @param ndots */ function bubDrawLine(ndots){ var ndot; dots.forEach(function (dot) { move(dot); // 循環比對粒子間的距離 for (var i = 0; i < ndots.length; i++) { ndot = ndots[i]; if (dot === ndot || ndot.x === null || ndot.y === null) continue; var xc = dot.x - ndot.x; var yc = dot.y - ndot.y; // 如果x軸距離或y軸距離大於max,則不計算粒子距離 if(xc > ndot.max || yc > lineDis) continue; // 兩個粒子之間的距離 var dis = xc * xc + yc * yc; // 如果粒子距離超過max,則不做處理 if( dis > lineDis ) continue; // 距離比 var ratio; // 如果是鼠標,則讓粒子向鼠標的位置移動 if (ndot === warea && dis < 20000) { dot.x -= xc * 0.01; dot.y -= yc * 0.01; } // 計算距離比 ratio = (lineDis - dis) / lineDis; // 粒子間連線 ctx.beginPath(); ctx.lineWidth = ratio / 2; ctx.strokeStyle = `rgba(${rgb},${rgb},${rgb},${ratio + 0.2}`; ctx.moveTo(dot.x, dot.y); ctx.lineTo(ndot.x, ndot.y); ctx.stroke(); } // 將已經計算過的粒子從數組中刪除 ndots.splice(ndots.indexOf(dot), 1); }); }
邏輯也比較簡單,就是遍歷數組,把遍歷到的粒子跟其他粒子進行逐個比對。當距離小於上面的lineDis的時候,就進行連線。為了減少計算量,每次計算過的粒子將會從用於計算的ndots數組中刪除,避免重復計算。同時如果兩個粒子的垂直距離和水平距離大於lineDis,那也就沒必要再算兩個粒子的距離了,直接不做處理,從而減少計算量。
其實這個計算用的還是所謂的笨方法,我此前有在想有什么更好的計算方法能更好的優化計算效率呢。然后想了一個方法並且進行了一個測試,就是先對粒子根據x軸進行快速排序,然后按順序進行比較,當比較到的粒子的水平距離大於lineDis的時候,就不用再比下去了。因為后面的都肯定會比當前粒子要更遠,想着就按照這樣會減少計算量應該會提升效率。但是我對兩個不同的計算方法都進行了耗時比較,結果還是原來的笨方法的性能更優。因為這個新方法每次都要重新排序,這個計算量也是蠻大的。然后就暫時沒想到其他了,如果讀者有更好的idea不妨分享一下。
同事有問我那個鼠標划過,粒子會聚起來的效果很神奇,怎么做的,其實這個效果比想象中簡單很多,而且在上面的代碼里我也給出來了。再給出一段保存鼠標位置的代碼,很簡單,就是鼠標移動的時候保存鼠標位置。
// 鼠標活動時,獲取鼠標坐標 var warea = {x: null, y: null}; var animateHeader = document.getElementById("animateHeader"); animateHeader.onmousemove = function (e) { e = e || window.event; warea.x = e.clientX + 10; warea.y = e.clientY; };
保存了鼠標位置后,在每次動畫循環的時候,把鼠標位置也當成一個粒子對象塞進數組進行比較:
// 每一幀循環的邏輯 function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); bubDrawLine([warea].concat(dots)); RAF(animate); }
而粒子往鼠標方向運動的代碼,其實就這么一小截:
// 如果是鼠標,則讓粒子向鼠標的位置移動 if (ndot === warea && dis < 20000) { dot.x -= xc * 0.01; dot.y -= yc * 0.01; }
計算鼠標與粒子的距離,當鼠標與粒子之間的距離小於一定的時候,把粒子的位置更新為 “當前位置 - 鼠標粒子距離 * 0.01”即可。然后就會形成粒子往鼠標位置移動的效果了。
整個效果就這樣完成了,很簡單,也很有意思,有興趣的可以去研究一下發掘一些更好玩的效果。
貼上這個demo的github地址:https://github.com/whxaxes/canvas-test/tree/gh-pages/src//Funny-demo/netparticle
這個demo是很早之前寫的,跟上面貼出來的代碼會有點出入,但是原理是一樣的。懂了原理,就可以自己去實現一個了。
如果覺得demo不錯,就在github給個star唄,當然也歡迎fork