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;
if (xc > ndot.max || yc > lineDis) continue ;
var dis = xc * xc + yc * yc;
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