用 Canvas 编织璀璨星空图

是不是还蛮酷的呢?本文我们就来一点一点分析怎么实现它!

分析

首先我们看看这个效果具体有那些要点。首先,这么炫酷的效果肯定是要用到 Canvas 了,每个星星可以看作为一个粒子,因此,整个效果实际上就是粒子系统了。此外,我们可以发现每个粒子之间是相互连接的,只不过离的近的粒子之间的连线较粗且透明度较低,而离的远的则相反。

开始 Coding

HTML 部分

这部分我就简单放了一个 标签,设置样式使其填充全屏。

然后为了让所有元素没有间距和内补,我还加了一条全局样式:

*{

margin:;

padding:;

}

JavaScript 部分

下面我们来写核心的代码。首先我们要得到那个 canvas 并得到绘制上下文:

varcanvasEl=document.getElementById('canvas');

varctx=canvasEl.getContext('2d');

varmousePos=[,];

紧接着我们声明两个变量,分别用于存储“星星”和边:

varnodes=[];

varedges=[];

下一步,我们做些准备工作,就是让画布在窗口大小发生变化时重新绘制,并且调整自身分辨率:

window.onresize=function(){

canvasEl.height=canvasEl.clientHeight;

if(nodes.length==){

constructNodes();

}

render();

};

window.onresize();// trigger the event manually.

我们在第一次修改大小后构建了所有节点,这里就要用到下一个函数(constructNodes)了

这个函数中我们随机创建几个点,我们用字典对象的方式存储这些点的各个信息:

functionconstructNodes(){

for(vari=;i

varnode={

drivenByMouse:i==,

x:Math.random()*canvasEl.width,

y:Math.random()*canvasEl.height,

vx:Math.random()*1-0.5,

vy:Math.random()*1-0.5,

radius:Math.random()>0.9?3+Math.random()*3:1+Math.random()*3

};

nodes.push(node);

}

nodes.forEach(function(e){

nodes.forEach(function(e2){

if(e==e2){

return;

}

varedge={

from:e,

to:e2

}

addEdge(edge);

});

});

}

为了实现后面一个更炫酷的效果,我给第一个点加了一个 drivenByMouse 属性,这个点的位置不会被粒子系统管理,也不会绘制出来,但是它会与其他点连线,这样就实现了鼠标跟随的效果了。

这里稍微解释一下 radius 属性的取值,我希望让绝大部分点都是小半径的,而极少数的点半径比较大,所以我这里用了一点小 tricky,就是用概率控制点的半径取值,不断调整这个概率阈值就能获取期待的半径随机分布。

点都构建完毕了,就要构建点与点之间的连线了,我们用到双重遍历,把两个点捆绑成一组,放到 edges 数组中。注意这里我用了另外一个函数来完成这件事,而没有直接用 edges.push() ,为什么?

假设我们之前连接了 A、B两点,也就是外侧循环是A,内侧循环是B,那么在下一次循环中,外侧为B,内侧为A,是不是也会创建一条边呢?而实际上,这两个边除了方向不一样以外是完全一样的,这完全没有必要而且占用资源。因此我们在 addEdge 函数中进行一个判断:

functionaddEdge(edge){

varignore=false;

edges.forEach(function(e){

if(e.from==edge.from&e.to==edge.to){

ignore=true;

}

if(e.to==edge.from&e.from==edge.to){

ignore=true;

}

});

if(!ignore){

edges.push(edge);

}

}

至此,我们的准备工作就完毕了,下面我们要让点动起来:

functionstep(){

nodes.forEach(function(e){

if(e.drivenByMouse){

return;

}

e.x+=e.vx;

e.y+=e.vy;

functionclamp(min,max,value){

if(value>max){

returnmax;

}elseif(value

returnmin;

}else{

returnvalue;

}

}

if(e.x=canvasEl.width){

e.vx *= -1;

e.x=clamp(,canvasEl.width,e.x)

}

if(e.y=canvasEl.height){

e.vy *= -1;

e.y=clamp(,canvasEl.height,e.y)

}

});

adjustNodeDrivenByMouse();

render();

window.requestAnimationFrame(step);

}

functionadjustNodeDrivenByMouse(){

nodes[].x+=(mousePos[]-nodes[].x)/easingFactor;

nodes[].y+=(mousePos[1]-nodes[].y)/easingFactor;

}

看到这么一大段代码不要害怕,其实做的事情很简单。这是粒子系统的核心,就是遍历粒子,并且更新其状态。更新的公式就是

v=v+a

s=s+v

a是加速度,v是速度,s是位移。由于我们这里不涉及加速度,所以就不写了。然后我们需要作一个边缘的碰撞检测,不然我们的“星星”都无拘无束地一点点飞~走~了~。边缘碰撞后的处理方式就是让速度矢量反转,这样粒子就会“掉头”回来。

还记得我们需要做的鼠标跟随吗?也在这处理,我们让第一个点的位置一点一点移动到鼠标的位置,下面这个公式很有意思,可以轻松实现缓动:

x = x + (t - x) / factor

其中 factor 是缓动因子,t 是最终位置,x 是当前位置。至于这个公式的解释还有个交互大神 Bret Victor 在他的演讲中提到过,视频做的非常好,有条(ti)件(zi)大家一定要看看: Bret Victor – Stop Drawing Dead Fish

好了,回到主题。我们在上面的函数中处理完了一帧中的数据,我们要让整个粒子系统连续地运转起来就需要一个timer了,但是十分不提倡大家使用 setInterval,而是尽可能使用 requestAnimationFrame,它能保证你的帧率锁定在

剩下的就是绘制啦:

functionrender(){

ctx.fillStyle=backgroundColor;

ctx.fillRect(,,canvasEl.width,canvasEl.height);

edges.forEach(function(e){

varl=lengthOfEdge(e);

varthreshold=canvasEl.width/8;

if(l>threshold){

return;

}

ctx.strokeStyle=edgeColor;

ctx.lineWidth=(1.0-l/threshold)*2.5;

ctx.globalAlpha=1.0-l/threshold;

ctx.beginPath();

ctx.moveTo(e.from.x,e.from.y);

ctx.lineTo(e.to.x,e.to.y);

ctx.stroke();

});

ctx.globalAlpha=1.0;

nodes.forEach(function(e){

if(e.drivenByMouse){

return;

}

ctx.fillStyle=nodeColor;

ctx.beginPath();

ctx.arc(e.x,e.y,e.radius,,2*Math.PI);

ctx.fill();

});

}

常规的 Canvas 绘图操作,注意 beginPath 一定要调用,不然你的线就全部穿在一起了… 需要说明的是,在绘制边的时候,我们先要计算两点距离,然后根据一个阈值来判断是否要绘制这条边,这样我们才能实现距离远的点之间连线不可见的效果。

到这里,我们的整个效果就完成了。如果不明白大家也可以去我文章开头放的 repo 离去看完整的源码。Have fun!!

源自:http://www.jianshu.com/p/f5c0f9c4bc39

声明:文章著作权归作者所有,如有侵权,请联系小编删除。

感谢 · 转发欢迎大家留言

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181126B10I2Q00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券