lerpでなめらかに追従するUIを作る方法【JavaScript】

lerpのアイキャッチ画像

1.lerpでなめらかに追従するUIを作る方法

UIアニメーションを作っていると、「目的地まで一瞬で移動するのではなく、少し遅れてなめらかに追いかけてほしい」と感じる場面があります。

たとえば、カーソルを追いかける丸い要素、ドラッグ後に少し遅れてついてくるカード、スクロール位置に合わせてゆっくり動く表示などです。

このような動きを作るときに便利なのが、lerpです。

lerpは、現在の値を目標の値へ少しずつ近づける考え方です。急に移動させるのではなく、「今の位置」と「行きたい位置」の間を少しずつ埋めていくことで、なめらかな追従感を作れます。

2.lerpとは何か

lerpは、Linear Interpolationの略です。日本語では線形補間と呼ばれます。

難しく聞こえますが、UIアニメーションで使う場合はかなりシンプルです。

現在の値を、目標値に少しだけ近づける処理だと考えると分かりやすいです。

current += (target - current) * 0.1;

この1行が、lerpを使ったなめらかな追従の基本です。

current が現在の位置、target が目標の位置です。 その差分に 0.1 を掛けることで、目標に向かって少しずつ近づいていきます。

補間の考え方をもう少し広く整理した記事として、InterpolationとFixed Time Stepの関係もあります。

3.一瞬で移動するUIとの違い

まず、lerpを使わない場合を考えてみます。

current = target;

この場合、現在位置はすぐに目標位置と同じになります。

処理としては分かりやすいですが、UIとしては少し硬い印象になります。マウスを動かした瞬間に要素もぴったり移動するため、動きに余韻がありません。

一方で、lerpを使うと次のようになります。

current += (target - current) * 0.1;

現在位置が目標位置へ少しずつ近づくため、動きに遅れや余韻が生まれます。

この「少し遅れて追いかける感じ」が、UIに柔らかさを出してくれます。

4.lerp関数を作る

毎回同じ式を書くと分かりにくくなるので、関数にしておきます。

function lerp(start, end, amount) {
  return start + (end - start) * amount;
}

この関数では、start が現在値、end が目標値、amount が近づく割合です。

たとえば amount0.1 にすると、毎フレーム10%ずつ目標に近づきます。

currentX = lerp(currentX, targetX, 0.1);

このように書くと、現在のX座標が目標のX座標へなめらかに近づいていきます。

5.amountの値で動きが変わる

lerpでは、amount の値が動きの印象を大きく変えます。

  • 0.05:かなりゆっくり追従する
  • 0.1:自然に遅れて追従する
  • 0.2:少し速めに追従する
  • 0.5:かなり素早く目標に近づく

値が小さいほど、ゆっくり追いかけるような動きになります。 値が大きいほど、目標位置に素早く近づきます。

UIでは、0.08 から 0.2 くらいの範囲が扱いやすいです。

動きの印象を調整する考え方は、easingの記事とも近い部分があります。

6.requestAnimationFrameと組み合わせる

lerpは、1回だけ実行してもあまり意味がありません。

毎フレーム少しずつ値を更新することで、なめらかな動きになります。

function animate() {
  currentX = lerp(currentX, targetX, 0.1);

  element.style.transform = `translateX(${currentX}px)`;

  requestAnimationFrame(animate);
}

animate();

このように requestAnimationFrame の中でlerpを使うことで、毎フレーム目標に近づくUIを作れます。

毎フレーム更新する仕組みについては、requestAnimationFrameの基本でも整理しています。

7.実験:カーソルをなめらかに追いかけるUI

ここでは、マウスやタッチ位置を目標として、丸い要素が少し遅れて追いかけるUIを作ります。

ポイントは、実際のポインター位置をそのまま要素に反映しないことです。

ポインター位置は target として保存し、表示する位置は current として別に持ちます。 そして、currenttarget に少しずつ近づけます。

観察ポイント

  • ポインターにぴったり追従せず、少し遅れて動く
  • 動きを止めても、少しだけ余韻が残る
  • lerpの値を変えると、追従の速さが変わる

この小さな遅れが、UIの動きを硬いものから柔らかいものに変えてくれます。

この実装のポイント

今回の実装では、ポインター位置を直接UIの位置にしていません。

targetX = clientX - rect.left;
targetY = clientY - rect.top;

まず、ポインターの位置を目標値として保存します。

そして、アニメーションループの中で現在位置を目標値に近づけます。

currentX = lerp(currentX, targetX, amount);
currentY = lerp(currentY, targetY, amount);

このように、入力された位置実際に表示する位置を分けることで、なめらかな遅れを作れます。

8.lerpが向いているUI

lerpは、次のようなUIに向いています。

  • カーソルを追いかける装飾
  • ドラッグに少し遅れてついてくるカード
  • スクロール量に応じて動く要素
  • 数値カウントやプログレス表示
  • カメラや背景のなめらかな追従

共通しているのは、目標値が変わり続けるUIです。

目標値が変わるたびに即座に反映するのではなく、現在値を少しずつ近づけることで、自然な動きを作れます。

9.lerpを使うときの注意点

lerpは便利ですが、ひとつ注意点があります。

それは、目標値に完全には到達しにくいことです。

毎回「差分の一部だけ」を進むため、目標に近づくほど移動量が小さくなります。 その結果、かなり近づいているけれど、数値上は完全に一致していない状態が残ることがあります。

見た目のUIでは問題にならないことも多いですが、処理上「到着したかどうか」を判定したい場合は、差分が小さくなったら目標値に丸めると扱いやすくなります。

if (Math.abs(targetX - currentX) < 0.1) {
  currentX = targetX;
}

見た目のなめらかさだけでなく、状態管理も必要なUIでは、このような到着判定を入れておくと安全です。

なめらかなUIを安定して動かすには、フレームレートとパフォーマンスの考え方も重要です。

10.まとめ

lerpは、現在の値を目標の値へ少しずつ近づけるための考え方です。

一瞬で移動するのではなく、少し遅れて追いかけることで、UIに柔らかさや余韻を作れます。

特に、カーソル追従、ドラッグ、スクロール連動、数値アニメーションなど、目標値が変わるUIと相性が良いです。

小さな式ですが、使いどころを覚えると、UIの動きがかなり自然になります。

まずは 0.1 前後の値から試して、追従の速さや重さがどう変わるか観察してみるのがおすすめです。

コメント

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA