スマホでリストをスクロールしたとき、
指を離しても「スーッ」と動き続けますよね。
あの気持ちいい動きは、ただのスクロールではなく
“慣性(Inertia)”で動いています。
この記事ではこの動きを、JavaScriptだけで再現します。
なお、今回の実装では requestAnimationFrame を使って毎フレーム位置を更新します。
requestAnimationFrame の基本は、先に「requestAnimationFrameの使い方」で整理しています。
1.基本の考え方
慣性スクロールの正体はシンプルです。
👉 「速度を持ったまま動き続ける」
① 速度を計算する
velocity = currentX - previousX- フレームごとの差分
- これが「今の速さ」
② 指を離したあとも使う
ドラッグ終了後も、この速度を使って動かします。
速度やフレームごとの差分を扱う考え方は、deltaTimeの記事でも詳しく整理しています。
今回の慣性スクロールでも、「1フレームごとにどれだけ動いたか」を見るのが重要です。
2.慣性のコアロジック
velocity *= 0.95
position += velocity0.95→ 摩擦(friction)- 徐々に減速して止まる
3.lerpとの違い
- lerp → 目的地に近づく
- inertia → 速度で動く
👉 別物です(ここ重要)
lerpは「現在位置から目標位置へなめらかに近づける」考え方です。
今回の inertia は目標位置ではなく、速度を減衰させながら動かす点が違います。
lerpの基本は「lerpの記事」の中でも使っています。
<div class="container" id="container">
<div class="track" id="track">
<div class="card">1</div>
<div class="card">2</div>
<div class="card">3</div>
<div class="card">4</div>
<div class="card">5</div>
</div>
</div>.container {
width: 100%;
overflow: hidden;
border: 1px solid #ddd;
cursor: grab;
}
.track {
display: flex;
gap: 16px;
padding: 20px;
will-change: transform;
}
.card {
min-width: 200px;
height: 120px;
background: #111;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
}const container = document.getElementById("container")
const track = document.getElementById("track")
let isDown = false
let startX = 0
let currentX = 0
let prevX = 0
let position = 0
let velocity = 0
const friction = 0.95
container.addEventListener("mousedown", e => {
isDown = true
startX = e.clientX
velocity = 0
})
window.addEventListener("mousemove", e => {
if (!isDown) return
const x = e.clientX
const delta = x - startX
prevX = currentX
currentX = delta
velocity = currentX - prevX
position += velocity
track.style.transform = `translateX(${position}px)`
})
window.addEventListener("mouseup", () => {
isDown = false
})
function animate() {
if (!isDown) {
velocity *= friction
position += velocity
if (Math.abs(velocity) < 0.1) {
velocity = 0
}
track.style.transform = `translateX(${position}px)`
}
requestAnimationFrame(animate)
}
animate()4.この実装のポイント
① velocityがすべて
- 速さを記録
- 離したあとも使う
② frictionで気持ちよさが決まる
- 0.9 → すぐ止まる
- 0.98 → 長く滑る
👉 UX調整ポイント
動きの気持ちよさを調整するという意味では、easingとも近い考え方です。
ただし、easingは時間に対する変化のカーブ、frictionは速度の減衰として考えると分かりやすいです。
③ requestAnimationFrameで継続
慣性スクロールは「1回の処理」ではなく、
毎フレーム少しずつ更新することで実現します。
そのために使うのが requestAnimationFrame です。
requestAnimationFrame の基本的な使い方は、こちらの記事で詳しく解説しています。
5.境界処理(重要)
このままだと無限にスクロールできます。
現実的には👇を入れる
position = Math.max(maxLeft, Math.min(position, maxRight))または
- 端で止める
- バウンスさせる
👉 UX設計の分かれ道
スクロール位置を使ってUIを制御する考え方は、Scroll ProgressやScroll Triggerでも使っています。
今回の慣性スクロールは「ユーザー操作後の動き」に注目したパターンです。
6.実験:Momentum Scroll Playground
以下を試してください:
- frictionを0.9〜0.98で変更
- 強くドラッグ vs 弱くドラッグ
- 速度の違いを体感
👉 「iOSっぽさ」はここで作れる
7.よくある失敗
- velocityを更新してない
- frictionが強すぎる
- requestAnimationFrameを使っていない
8.まとめ
慣性スクロールの本質はシンプルです。
👉 「速度を持たせて、徐々に減速する」
この考え方は、
- カルーセル
- スワイプUI
- ゲーム
などにも応用できます。

コメントを残す