.follower {
  background: black;
  border-radius: 10px;
  display: block;
  height: 10px;
  position: fixed;
  width: 10px;
}
body {
  margin: 0;
  overflow: hidden;
}
canvas {
  width:100%;
}
console.clear();

const clamp = function (a, b, v) {
  return Math.min(b, Math.max(a, v));
};
const lerp = function (a, b, p) {
  return a + p * (b - a);
};
const smoothstep = function(edge0, edge1, x) {
  const t = clamp(0.0, 1.0, (x - edge0) / (edge1 - edge0))
  return t * t * (3.0 - 2.0 * t)
}

class Follower {
  constructor({ target, radius = 20, distance = 20, springK = 0.18, damping = 0.6, forward = 0.05 }) {
    this.target = target;
    this.distance = distance;
    this.a = 0;
    this.x = this.lx = 0;
    this.y = this.ly = 0;
    this.vx = 0;
    this.vy = 0;
    this.radius = radius;
    this.springK = springK;
    this.damping = damping;
    this.forward = forward;
  }
  
  solve(d) {
    const dx = this.target.x - this.x;
    const dy = this.target.y - this.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    const angle = Math.atan2(dy, dx);
    
    const tx = this.target.x - Math.cos(angle) * this.distance;
    const ty = this.target.y - Math.sin(angle) * this.distance;
    
    this.vx += (tx - this.x) * this.springK;
    this.vy += (ty - this.y) * this.springK;
    
    this.vx *= this.damping;
    this.vy *= this.damping;
    
    this.x += this.vx;
    this.y += this.vy;
    
    this.target.x += this.vx*this.forward
    this.target.y += this.vy*this.forward
  }
}

const s = {x: window.innerWidth/2+200, y: window.innerHeight/2-200}
const m = { x: s.x, y: s.y, a: Math.PI * .25 };
const followers = [
  new Follower({ target: m })
];
let lr = 70;
for (let i = 0; i < 20; i++) {
  // const r = Math.cos(i*.3-1.5)*20+25;
  const t = Math.abs(i-2);
  const r = lerp(5, 80, smoothstep(10, 2, t)**2);
  const nr = lerp(10, 50, smoothstep(15, 1, t));
  followers.push(new Follower({ target: followers[followers.length - 1], radius: r, distance: lr }));
  lr = nr;
}

const c = document.createElement('canvas');
c.width = window.innerWidth*2;
c.height = window.innerHeight*2;
const ctx = c.getContext('2d');
document.body.appendChild(c);

window.addEventListener('resize', (e)=> {
c.width = window.innerWidth*2;
c.height = window.innerHeight*2;
})
window.addEventListener('pointermove', (e) => {
  m.x = e.x;
  m.y = e.y;
});

let ld = 0, rd = 0;
const runner = (d) => {
  requestAnimationFrame(runner);
  
  if(ld>0) {
    rd += d-ld;
    
    window.ld = ld
    window.rd = rd;

    if(rd<1000) {
      m.x = lerp(s.x, s.x-400, smoothstep(0, 1000, rd))
      m.y = lerp(s.y, s.y+400, smoothstep(0, 1000, rd))
    }
  }
  ld = d;

  ctx.clearRect(0,0,c.width,c.height)
  followers.forEach((e, i) => {
    e.solve(d);
  });
  followers.forEach((e, i) => {
    const e2 = followers[i+1];
    
    ctx.fillStyle = '#222222';
    ctx.beginPath()
    ctx.arc(e.x*2,e.y*2, e.radius, 0, Math.PI*2);
    ctx.fill()
    
    if(e2 instanceof Follower) {
      const a = Math.atan2(e2.y-e.y,e2.x-e.x)+Math.PI * .5
      
      const x1 = e.x + Math.cos(a) * e.radius*.5;
      const y1 = e.y + Math.sin(a) * e.radius*.5;
      const x2 = e2.x - Math.cos(a) * e2.radius*.5;
      const y2 = e2.y - Math.sin(a) * e2.radius*.5;

      const x3 = e2.x + Math.cos(a) * e2.radius*.5;
      const y3 = e2.y + Math.sin(a) * e2.radius*.5;
      const x4 = e.x - Math.cos(a) * e.radius*.5;
      const y4 = e.y - Math.sin(a) * e.radius*.5;
      
      
      ctx.beginPath()
      ctx.moveTo(x1*2,y1*2);
      ctx.lineTo(x4*2,y4*2);
      ctx.lineTo(x2*2,y2*2);
      ctx.lineTo(x3*2,y3*2);
      ctx.closePath();
      ctx.fill();
     
    }
   
  });
}
requestAnimationFrame(runner);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.