타임라인 시퀀싱

Timeline sequencing

위치·스케일·투명도 같은 여러 트랙을 하나의 재생 헤드(playhead)가 동시에 관통하며 값을 읽어내는 구조. 트랙마다 구간이 달라도 하나의 시간 축으로 동기화됩니다.

다른 이름: Animation timelineKeyframe sequencing
···
html
<div class="tl">
  <div class="stage"><div class="obj" id="obj"></div></div>
  <div class="tracks" id="tracks">
    <div class="track"><span class="lbl">X</span><div class="lane"><div class="seg s1"></div></div></div>
    <div class="track"><span class="lbl">Scale</span><div class="lane"><div class="seg s2"></div></div></div>
    <div class="track"><span class="lbl">Opacity</span><div class="lane"><div class="seg s3"></div></div></div>
    <div class="playhead" id="ph"></div>
  </div>
</div>
css
.tl{position:absolute;inset:6%;display:grid;grid-template-rows:1fr auto;gap:10px}
.stage{position:relative;border:1px solid var(--line);border-radius:10px;background:var(--surface);overflow:hidden}
.obj{position:absolute;top:50%;left:4%;width:clamp(18px,7vmin,30px);height:clamp(18px,7vmin,30px);margin-top:-15px;border-radius:8px;background:var(--accent)}
.tracks{position:relative;display:flex;flex-direction:column;gap:6px;padding:2px 0}
.track{display:flex;align-items:center;gap:8px}
.lbl{width:38px;flex:none;font:10px/1 ui-monospace,monospace;color:var(--muted);text-align:right}
.lane{position:relative;flex:1;height:8px;background:var(--line);border-radius:4px;overflow:hidden}
.seg{position:absolute;top:0;bottom:0;border-radius:4px}
.s1{left:0%;width:60%;background:var(--accent)}
.s2{left:30%;width:50%;background:var(--accent-2)}
.s3{left:50%;width:50%;background:var(--accent-3)}
.playhead{position:absolute;top:-52px;bottom:-2px;width:2px;background:var(--fg);opacity:.5}
js
const obj = document.getElementById('obj');
const ph = document.getElementById('ph');
const DUR = 3200;
function clamp01(n) { return Math.max(0, Math.min(1, n)); }
const t0 = performance.now();
function loop(now) {
  const t = ((now - t0) % DUR) / DUR;
  ph.style.left = (t * 100) + '%';

  const px = clamp01(t / 0.6) * 78;
  let sc = 1;
  if (t >= 0.3) sc = 1 + clamp01((t - 0.3) / 0.5) * 0.7;
  let op = 1;
  if (t >= 0.5) op = 1 - clamp01((t - 0.5) / 0.5) * 0.75;

  obj.style.left = (4 + px) + '%';
  obj.style.transform = 'translateY(-50%) scale(' + sc.toFixed(2) + ')';
  obj.style.opacity = String(op.toFixed(2));
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

각 트랙은 자신만의 구간(시작~끝 진행률)과 그 구간 안에서의 값 변화를 갖습니다. 재생 헤드가 0에서 1로 진행하면서, 현재 지점이 어떤 트랙의 구간 안에 들어와 있는지를 확인하고 그 트랙의 값을 요소에 적용합니다. 한 트랙이 끝나기 전에 다음 트랙이 시작할 수 있어서, "이동하다가 중간부터 커지고, 커지는 도중 옅어지는" 식으로 겹치는 애니메이션을 만들 수 있습니다.

구현은 트랙마다 [시작 진행률, 끝 진행률]을 주고, 현재 progress가 그 구간 안이면 (progress - 시작) / (끝 - 시작)로 트랙 내부 진행률을 다시 0~1로 정규화해 보간하는 식입니다. After Effects의 그래프 에디터나 GSAP Timeline이 이 구조를 GUI·API로 제공하는 대표적인 도구입니다 — 레이어마다 트랙을 놓고 타임라인 바를 드래그해 겹치는 구간을 눈으로 맞춥니다.

트랙이 2~3개일 땐 손으로 구간을 맞춰도 되지만, 4~5개를 넘어가면 겹치는 지점을 코드로 관리하기 어려워집니다. 그 지점부터는 전용 타임라인 도구를 쓰는 게 버그를 줄입니다.

언제 쓰나

여러 속성이 서로 다른 타이밍으로 겹쳐 움직여야 하는 복합 연출(로고 인트로, 온보딩 시퀀스)에 씁니다. 속성 하나만 바뀌는 단순한 전환에는 과한 구조입니다.