스프링 체인

Spring chain

노드들이 한 줄로 연결되어, 각 노드가 앞 노드를 스프링으로 뒤쫓는 구조. 리더가 움직이면 뒤로 갈수록 지연과 출렁임이 누적돼 꼬리·로프 같은 움직임이 됩니다.

다른 이름: 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);

노드는 하나의 스프링 애니메이션(목표를 향해 velocity += (target - pos) * stiffness; velocity *= damping; pos += velocity)을 그대로 재사용하되, 각 노드의 target이 고정된 좌표가 아니라 "바로 앞 노드의 현재 위치"입니다. 1번 노드는 리더(마우스, 자동 경로 등)를 쫓고, 2번은 1번을, 3번은 2번을 쫓는 식으로 체인이 이어집니다.

앞 노드가 빠르게 방향을 바꾸면 뒤 노드는 그 변화를 즉시 따라가지 못하고 스프링 특유의 지연·오버슈트를 겪습니다 — 이 지연이 노드 인덱스가 클수록 누적되어, 리더의 급격한 움직임이 꼬리 끝에서는 크고 느슨한 채찍질처럼 보입니다. 각 노드의 stiffness·damping을 동일하게 두면 균일한 로프처럼, 뒤로 갈수록 stiffness를 낮추면 더 나긋나긋한 꼬리처럼 보입니다.

노드 수가 많아지면(20개 이상) 매 프레임 연산량이 선형으로 늘어나지만 각 노드는 독립적이라 위치 갱신 자체는 병렬화하기 쉽습니다 — 성능보다는 개수가 적을 때부터 "체인처럼 보이는" 최소 노드 수(보통 6~10개)를 먼저 찾는 게 관건입니다.

언제 쓰나

커서를 따라다니는 트레일, 캐릭터의 꼬리·머리카락, 로프 시뮬레이션처럼 "뒤로 갈수록 지연되는" 것이 자연스러운 대상에 씁니다. 즉시 반응해야 하는 UI 요소에는 부적합합니다.