当前位置: 首页 > Web前端 > CSS

Canvas绘制经典星空连线效果

时间:2023-03-30 13:31:26 CSS

废话不多说,先上图:第一次看到这个效果是在https://www.mengxiaozhu.cn/后来知乎的登录页也开始了使用https://www.zhihu.com/网上很多地方还在用,效果还是不错的。看了之后觉得很有意思,于是研究了一下原理开始码字:先写一个canvas标签并添加一些默认样式:*{margin:0;padding:0;}body{overflow:hidden;}这里的overflow:hidden是为了不让滚动条出现,开始写JS:首先我们要获取canvas,获取绘图上下文:varcanvasEl=document.getElementById('画布');varctx=canvasEl.getContext('2d');varmousePos=[0,0];然后我们声明两个变量,用来存储“星”和边:varnodes=[];varedges=[];然后我们定义一些其他变量:vareasingFactor=5.0;//缓和因子varbackgroundColor='#000';//背景颜色varnodeColor='#fff';//点颜色varedgeColor='#fff';//边缘颜色varpageWidth=window.innerWidth,//窗口宽度pageHeight=window.innerHeight;//窗口高度设置画布覆盖整个屏幕的大小:window.onresize=function(){canvasEl.width=pageWidth;canvasEl.height=pageHeight;if(nodes.length==0){constructNodes();}render();};window.onresize();准备工作完成,我们开始构建点:functionconstructNodes(){for(vari=0;i<100;i++){varnode={drivenByMouse:i==0,x:Math.random()*canvasEl.width,y:Math.random()*canvasEl.height,vx:Math.random()*1-0.5,vy:Math.random()*1-0.5,半径:Math.random()>0.9?3+Math.random()*3:1+Math.random()*3};节点.推送(节点);}nodes.forEach(function(e){nodes.forEach(function(e2){if(e==e2){return;}varedge={from:e,to:e2}addEdge(edge);});});}先创建100个点,每个点设置6个属性,drivenByMouse属性只有第一个点为true,其他点为false。第一个点用作鼠标跟随点,不显示。可以用x,y作为点的初始位置与其他点连接得到它是画布中的一个随机点,vx,vy代表点的初始速度,取值范围是-0.5到-0.5之间的随机数0.5,radius表示点的半径,大部分点小,少数点大。建立完所有点后,就该建立点之间的连接了。我们使用二次遍历,将两个点捆绑成一个组,放在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;}});如果(!忽略){edges.push(edge);}}至此,我们的准备工作就完成了,我们需要让点动起来了:vx;e.y+=e.vy;functionclamp(min,max,value){if(value>max){returnmax;}elseif(value=canvasEl.width){e.vx*=-1;e.x=clamp(0,canvasEl.width,e.x)}if(e.y<=0||e.y>=canvasEl.height){e.vy*=-1;e.y=clamp(0,canvasEl.height,e.y)}});adjustNodeDrivenByMouse();使成为();window.requestAnimationFrame(step);}functionadjustNodeDrivenByMouse(){nodes[0].x+=(mousePos[0]-nodes[0].x)/easingFactor;nodes[0].y+=(mousePos[1]-nodes[0].y)/easingFactor;}这段代码是遍历粒子并更新它们的状态根据一个简单的物理公式s=s+v,每次执行会更新下一步的状态到点。adjustNodeDrivenByMouse函数将第一个点作为鼠标的跟随点,easingFactor为缓动因子,可以使点的移动比鼠标的移动慢一点。那么我们就需要一个定时器让整个粒子系统持续运行,但是不建议使用setInterval,而是尽量使用requestAnimationFrame,这样可以保证你的帧率锁定在当前浏览器的频率,一般是60HZ。剩下的就是绘制functionrender(){ctx.fillStyle=backgroundColor;ctx.fillRect(0,0,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/阈值)*2.5;ctx.globalAlpha=1.0-l/阈值;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,0,2*Math.PI);ctx.fill();});}functionlengthOfEdge(edge){returnMath.sqrt(Math.pow((edge.from.x-edge.to.x),2)+Math.pow((edge.from.y-edge.to.y),2));}绘制的时候需要判断直线的长度。如果线的长度大于某个值,则不会绘制线,如果在范围内,颜色的粗细和透明度都与线的长度有关。除了第一个鼠标跟随点外,其他的点都可以画进去,最后加上鼠标移动事件启动定时器:window.onmousemove=function(e){mousePos[0]=e.clientX;mousePos[1]=e.clientY;}window.requestAnimationFrame(step);你完成了!!