че то похожее делал когда то - нашел такой скрипт и переделал под себя. оставлю - может поможет
это в html:
<canvas id="c" width="1920" height="616"></canvas>
это в js:
function rand(t,i){return Math.random()*(i-t)+t}function Impulse(){this.x=cx,this.y=cy,this.ax=0,this.ay=0,this.vx=0,this.vy=0,this.r=1}function Chain(){this.branches=[],this.impulse=new Impulse,this.branches.push(new Branch({chain:this,attractor:this.impulse}))}function Branch(t){this.entities=[],this.chain=t.chain,this.avoiding=0;for(var i,h=0;entityCount>h;h++)i=new Entity({branch:this,i:h,x:cx,y:cy,radius:1+(entityCount-h)/entityCount*5,damp:.2,attractor:0===h?t.attractor:this.entities[h-1]}),this.entities.push(i)}function Entity(t){this.branch=t.branch,this.i=t.i,this.x=t.x,this.y=t.y,this.vx=0,this.vy=0,this.radius=t.radius,this.attractor=t.attractor,this.damp=t.damp}function loop(){requestAnimationFrame(loop),c.globalCompositeOperation="destination-out",c.fillStyle="rgba(0, 0, 0, 1)",c.fillRect(0,0,a.width,a.height),c.globalCompositeOperation="lighter",chains.forEach(function(t){t.step()}),tick++}function resize(){a.width=window.innerWidth,a.height=window.innerHeight,w=a.width,h=a.height,cx=w/2,cy=h/2}var a=document.getElementById("c"),c=a.getContext("2d"),chains=[],chainCount=30,entityCount=10,w=a.width,h=a.height,cx=w/2,cy=h/2,mx=cx,my=cy,md=0,tick=0,maxa=2,maxv=1,avoidTick=20,avoidThresh=50,TWO_PI=2*Math.PI;Impulse.prototype.step=function(){this.x+=this.vx,this.y+=this.vy,(this.x+this.r>=w||this.x<=this.r)&&(this.vx=0,this.ax=0),(this.y+this.r>=h||this.y<=this.r)&&(this.vy=0,this.ay=0),this.x+this.r>=w&&(this.x=w-this.r),this.x<=this.r&&(this.x=this.r),this.y+this.r>=h&&(this.y=h-this.r),this.y<=this.r&&(this.y=this.r),md&&(this.vx+=.03*(mx-this.x),this.vy+=.03*(my-this.y)),this.ax+=rand(-.4,.4),this.ay+=rand(-.4,.4),this.vx+=this.ax,this.vy+=this.ay,this.ax*=Math.abs(this.ax)>maxa?.75:1,this.ay*=Math.abs(this.ay)>maxa?.75:1,this.vx*=Math.abs(this.vx)>maxv?.75:1,this.vy*=Math.abs(this.vy)>maxv?.75:1},Chain.prototype.step=function(){this.impulse.step(),this.branches.forEach(function(t){t.step()}),this.branches.forEach(function(t){t.draw()})},Branch.prototype.step=function(){for(var t=chains.length;t--;){var i=this.chain.impulse,h=chains[t].impulse,s=h.x-i.x,a=h.y-i.y,n=Math.sqrt(s*s+a*a);!md&&i!==h&&avoidThresh>n&&(i.ax=0,i.ay=0,i.vx-=.1*s,i.vy-=.1*a,this.avoiding=avoidTick)}this.entities.forEach(function(t){t.step()}),this.avoiding>0&&this.avoiding--},Branch.prototype.draw=function(){var t=this;c.beginPath(),c.moveTo(this.entities[0].x,this.entities[0].y),this.entities.forEach(function(t,i){i>0&&c.lineTo(t.x,t.y)}),c.strokeStyle="hsla("+(md?120:t.avoiding?0:200)+", 70%, 60%, 0.3)",c.stroke(),this.entities.forEach(function(i,h){c.save(),c.translate(i.x,i.y),c.beginPath(),c.rotate(i.rot),c.fillStyle=0===i.i?md?"#6c6":t.avoiding?"#c66":"#6bf":"hsla("+(md?120:t.avoiding?0:200)+", 70%, "+Math.min(50,5+i.av/maxv*20)+"%, "+(entityCount-h)/entityCount+")",c.fillRect(-i.radius,-i.radius,2*i.radius,2*i.radius),c.restore()})},Entity.prototype.step=function(){this.vx=(this.attractor.x-this.x)*this.damp,this.vy=(this.attractor.y-this.y)*this.damp,this.x+=this.vx,this.y+=this.vy,this.av=(Math.abs(this.vx)+Math.abs(this.vy))/2;var t=this.attractor.x-this.x,i=this.attractor.y-this.y;this.rot=Math.atan2(i,t)},window.addEventListener("resize",resize),window.addEventListener("mousedown",function(){md=1}),window.addEventListener("mouseup",function(){md=0}),window.addEventListener("mousemove",function(t){mx=t.clientX,my=t.clientY}),resize();for(var i=0;chainCount>i;i++)chains.push(new Chain);loop();