Web UIでは、スクロールで動く表現と、ドラッグで操作する表現は別々に扱われることが多いです。
スクロール系の基本は、Scroll Trigger Animation や Scroll Progress でも扱っています。
しかし実際のUIでは、
- スクロールで進む
- ドラッグでも動かせる
- 操作後に少し慣性が残る
というように、複数の入力が同じ動きを操作することがあります。
今回は、Scroll + Drag Hybrid UIとして、スクロールとドラッグを同じ位置情報に接続するUIを作ってみます。
1.Scroll + Drag Hybrid UIとは
Scroll + Drag Hybrid UIは、スクロール操作とドラッグ操作を、同じUIの移動量に反映させる考え方です。
たとえば、横に並んだカードを、
- ページスクロールで少しずつ移動させる
- マウスやタッチ操作で直接ドラッグする
- 離したあとに少しだけ慣性で流す
といった形です。
単なるスクロール演出ではなく、ユーザーが直接触っている感覚を加えられるのが特徴です。
2.今回作るもの
今回は、横並びカードのUIを作ります。
スクロールするとカード列が少し動き、さらにカードエリアをドラッグすると、同じカード列を直接動かせるようにします。
ポイントは、scroll用の値とdrag用の値を分けすぎないことです。
let targetX = 0;
let currentX = 0;
let velocity = 0;このように、UIの位置を管理する値を中心にして、スクロールもドラッグもそこへ反映させます。
3.なぜ共通のstateにするのか
スクロール用の処理、ドラッグ用の処理、描画用の処理を完全に分けてしまうと、UIの状態がずれやすくなります。
たとえば、
- スクロールでは動いている
- ドラッグでは別の位置を見ている
- 離した瞬間に位置が飛ぶ
という問題が起きやすくなります。
そこで今回は、すべての入力を targetX に集めます。
targetX += delta;スクロールでも、ドラッグでも、最終的には同じ値を動かすだけにします。
4.requestAnimationFrameで描画をまとめる
入力イベントの中で直接 transform を更新し続けると、処理が散らばります。
そこで、描画は requestAnimationFrame にまとめます。
function animate() {
currentX += (targetX - currentX) * 0.12;
track.style.transform = `translateX(${currentX}px)`;
requestAnimationFrame(animate);
}入力は値を変えるだけ。
描画は毎フレームまとめて行う。
この分離をしておくと、スクロール、ドラッグ、慣性をあとから足しやすくなります。
描画ループの基本は、requestAnimationFrameの使い方 で詳しく扱っています。
5.Drag操作を追加する
ドラッグでは、前回のマウス位置との差分を使います。
const dx = e.clientX - lastX;
targetX += dx;
lastX = e.clientX;この dx を targetX に足すだけで、カード列を直接動かせます。
また、最後の移動量を velocity に入れておくと、マウスを離したあとに慣性をつけられます。
velocity = dx;ドラッグ操作そのものの基本は、Drag Interaction UI の記事でも整理しています。
6.慣性を追加する
ドラッグを離したあと、少しだけ動きが残るようにします。
if (!isDragging) {
targetX += velocity;
velocity *= 0.92;
}velocity を少しずつ小さくすることで、自然に止まるような動きになります。
この考え方は、Inertia Scroll / Momentum UIの記事とも近いです。
7.動きすぎを防ぐ
ドラッグUIでは、どこまでも動いてしまうと見た目が崩れます。
そのため、今回は移動範囲を制限します。
targetX = Math.max(minX, Math.min(maxX, targetX));最小値と最大値を決めて、その範囲から出ないようにします。
8.実験:Scroll vs Drag — Which Feels Better?
下のデモでは、スクロールとドラッグの両方でカード列を動かせます。
スクロールでゆっくり流れる感じと、ドラッグで直接操作する感じの違いを比べてみてください。
9.まとめ
Scroll + Drag Hybrid UIは、スクロールとドラッグを別々の機能として扱うのではなく、同じ状態に接続するUIです。
入力、状態、描画を分ける考え方は、状態管理の記事 や lerpの記事 ともつながります。
今回のポイントは次の3つです。
- 入力イベントでは値だけを変える
- 描画は
requestAnimationFrameにまとめる - スクロールもドラッグも同じ
targetXを動かす
この形にしておくと、あとから慣性や補間、スナップ処理なども追加しやすくなります。
単なるスクロール演出よりも、ユーザーが直接触っている感覚を作れるのが、Hybrid UIの面白いところです。

コメントを残す