Spring chain

스프링 체인

A chain of nodes where each one springs toward the node ahead of it. Move the leader and the delay and wobble compound down the chain — the motion of a tail, or a rope.

Also known as: Follow-the-leaderChained springTail/rope follow
···
html
<svg class="chainsvg" viewBox="0 0 200 100">
  <polyline id="line" class="line" fill="none"/>
  <g id="nodes"></g>
</svg>
css
.chainsvg{width:88%;height:78%}
.line{stroke:var(--line);stroke-width:2}
circle.node{fill:var(--accent)}
js
const N = 9;
const nodes = Array.from({ length: N }, () => ({ x: 100, y: 50, vx: 0, vy: 0 }));
const line = document.getElementById('line');
const group = document.getElementById('nodes');
const dots = nodes.map((_, i) => {
  const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
  c.setAttribute('r', String(6 - i * 0.35));
  c.setAttribute('class', 'node');
  c.style.opacity = String(1 - i * 0.06);
  group.appendChild(c);
  return c;
});

const start = performance.now();
function leaderPos(t) {
  const a = t * 0.0032;
  return { x: 100 + Math.sin(a) * 76, y: 50 + Math.sin(a * 2.3) * 30 };
}

function loop(now) {
  const lp = leaderPos(now - start);
  let target = lp;
  nodes.forEach((n, i) => {
    const k = 0.09, damp = 0.86;
    n.vx = (n.vx + (target.x - n.x) * k) * damp;
    n.vy = (n.vy + (target.y - n.y) * k) * damp;
    n.x += n.vx;
    n.y += n.vy;
    dots[i].setAttribute('cx', n.x.toFixed(1));
    dots[i].setAttribute('cy', n.y.toFixed(1));
    target = n;
  });
  line.setAttribute('points', [lp, ...nodes].map((n) => n.x.toFixed(1) + ',' + n.y.toFixed(1)).join(' '));
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

Each node reuses the same single spring update (velocity += (target − pos) × stiffness; velocity *= damping; pos += velocity), except its target isn't a fixed point — it's the current position of the node right before it. Node 1 chases a leader (a cursor, an auto-path); node 2 chases node 1; node 3 chases node 2, and so on down the chain.

When the leader changes direction sharply, the next node can't follow instantly — it carries the spring's usual lag and overshoot. That lag compounds with each index down the chain, so a sudden move at the head reads as a large, loose whip by the time it reaches the tail. Keep stiffness and damping identical across nodes for a uniform rope feel; lower stiffness toward the tail for something softer and more tail-like.

With many nodes (20+) the per-frame work grows linearly, but each update is independent, so it parallelizes easily. The real tuning problem isn't performance — it's finding the smallest node count that still reads as a chain, usually somewhere around 6–10.

When to use

Use it for cursor trails, a character's tail or hair, or rope sims — anywhere lag increasing toward the back reads as natural. Not a fit for UI elements that need to respond instantly.