.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);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.