オフセット
オフセットの作り方
5 つの円が左へ送られ、大きさの波が右から左へ伝わって見える。この波の正体は、ひとつの動きを円ごとに少しずつ時間をずらして重ねたものだ。そのズレこそが「オフセット」だ。コードつきで、組み立ての考え方から順に見ていく。
- Published
- 2026年6月14日
- Topics
- Offset · Stagger · Easing · SVG
ひとつの動きと、時間をずらした重ね方を用意する
- 手で決めるのは、円ひとつが左へすべる 1 本の動きと、スロットごとの大きさの並び
- その 1 本の動きを、少しずつ時間をずらして重ねる — これが「オフセット」
- 右へ向かう複製のうち 1 つだけ、ひとつの大きさを少し増やしてある
const wrap = (value, span) => ((value % span) + span) % span;
// フレーム番号 → いま何割すすんだか(0 から 1)。ひと送りのあいだ前へ進み、送り切ったら待つ
const progressAt = (frame) => {
const local = wrap(frame, periodFrames);
const subcycle = Math.floor(local / subcycleFrames);
const within = local - subcycle * subcycleFrames;
return cubicBezier(...easeHandles)(Math.min(within / activeFrames, 1));
};progressAt が返すのは、ひと送りのあいだに 0 から 1 へ動くひとつの数だ。あとで見るとおり、この同じ数が円の送り(位置)と大きさの両方を動かす。cubicBezier に渡す easeHandles は CSS の cubic-bezier() と同じ並びで、bezier-easing のような既製のライブラリにもそのまま渡せる。頭打ちにしてあるから、activeFrames のあいだだけ前へ送って、残りはそのまま次まで待つ。
const slotCount = slotKeys.length;
const dupCount = Math.round(periodFrames / subcycleFrames);
// スロットの大きさ。時間ずらしの複製のうち 1 つだけ、右のひとつが別の値を返す
const valueOf = (slot, dup) => {
if (dup === overrideDup && slot === overrideSlot) return overrideValue;
return slot >= 0 && slot < slotCount ? slotKeys[slot] : 0;
};
function dotsAt(frame) { // フレーム番号 → 各スロットの位置と大きさ。1 つの progress が両方を決める
const subcycle = Math.floor(wrap(frame, periodFrames) / subcycleFrames);
const progress = progressAt(frame);
return slotKeys.map((_, slot) => {
const dup = wrap(subcycle + slot - (slotCount - 1), dupCount);
return {
coord: anchor + spacing * (slot - progress),
value: valueOf(slot, dup) * (1 - progress) + valueOf(slot - 1, dup) * progress,
slot,
};
});
}ここがオフセットの正体だ。dotsAt は別々の波を描いていない — dup で複製を選び、ひとつの progress を時間差で読んでいるだけだ。右から左へ伝わる「大きさの波」は、同じ動きを少しずつ遅らせて重ねた見え方にすぎない。途中で大きさがふつうに戻っていくように見えるのも、増やしたひとつの値が、隣の共通の値へ補間されていくからで、別の戻し処理があるわけではない。
画面に置くのは円だけだ。dotsAt が返した coord を横位置に、value を半径にして、ひとつの基準線の上にならべる。大きさがほぼ 0 のスロットは描かず外す。動かすのは requestAnimationFrame で、経過した時間にコマ数を掛けてフレーム番号を出し、periodFrames で wrap して dotsAt に渡せば、このデモと同じループになる。すすみが periodFrames で一周し、複製の選び方も一周ぶんで元へ戻るので、継ぎ目は出ない。
遊ぶなら大きさの並びを書き換える。slotKeys の数をいじれば波の山が高く・低くなり、spacing を変えれば送る幅が変わる。増やしたひとつ(overrideValue)を消せば、波はどこも同じ大きさで流れる。subcycleFrames を縮めれば、送りも複製の間隔もせわしなくなる。色は決め打ちにせずページの地の色を受け取るので、円を四角に変えても、この仕組みはそのまま使える。