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 が近づく割合です。
たとえば amount を 0.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 として別に持ちます。 そして、current を target に少しずつ近づけます。
観察ポイント
- ポインターにぴったり追従せず、少し遅れて動く
- 動きを止めても、少しだけ余韻が残る
- 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 前後の値から試して、追従の速さや重さがどう変わるか観察してみるのがおすすめです。

コメントを残す