トップページの WORKS ストリップを、自動で流れつつ手動でも動かせるマーキーにした話です。CSS アニメーションだけでは「触れない」ので、JS で scrollLeft を駆動する方式に切り替えました。

仕組み:複製行と 50% 巻き戻し

コンテンツを 2 セット並べ、スクロール位置が 1 セットぶんを超えたら同じ絵柄の位置へ巻き戻します。ドラッグで逆走しても、端で反対側へワープするので無限に回せます。

複製行で無限ループするマーキーの仕組み

const tick = (now: number) => {
  raf = requestAnimationFrame(tick);
  const dt = (now - last) / 1000;
  last = now;
  if (!paused && !dragging) pos += (half / LOOP_SECONDS) * dt;
  if (pos >= half) pos -= half; // 無限ループの巻き戻し
  if (pos < 0) pos += half;
  el.scrollLeft = pos;
};

ハマった罠:pointer capture と click

ドラッグ実装で pointerdown の瞬間に setPointerCapture すると、click イベントがキャプチャ元へ retarget されてカードのリンクが一切反応しなくなります

// NG: pointerdown で即キャプチャ → 通常クリックが死ぬ
// OK: 6px 動いて「ドラッグ確定」してからキャプチャする
if (!dragged && Math.abs(dx) > 6) {
  dragged = true;
  wrap.setPointerCapture(e.pointerId);
}

この手の不具合はビルドが通っても気づけません。実際にヘッドレスブラウザでクリック遷移まで検証して初めて見つかりました。