イージング関数
進行 0〜1 を「歪めて」緩急をつける関数。linear から elastic まで 30 種類を実装。
easings.net 互換。
イージングとは
トゥイーンの本体は lerp(a, b, t)。t をそのまま渡すと等速直線。
これを「最初ゆっくり、後で速く」みたいに歪めるのがイージング。
t (入力) linear easeInOut
──── ───── ─────
0 ──→ 0 0 ──→ 0 0 ──→ 0
0.5 ──→ 0.5 0.5 ──→ 0.5 0.5 ──→ 0.5
0.25──→ 0.25 0.25──→ 0.25 0.25 ──→ 0.10
0.75──→ 0.75 0.75──→ 0.75 0.75 ──→ 0.90
入力 t も出力も0〜1を保つのが約束。0 と 1 を必ず通り、その間で曲がる。
命名規則
- easeIn〜: 始まりがゆっくり(先細り)
- easeOut〜: 終わりがゆっくり(後細り、UI で最も自然)
- easeInOut〜: 両端ゆっくり、中間速い
典型的な選択
- UI の登場アニメ → easeOutQuad / easeOutCubic
- 消滅・退場 → easeInQuad / easeInCubic
- 状態遷移 → easeInOutCubic
- 跳ねる → easeOutBack / easeOutBounce
- ゴム的に弾む → easeOutElastic
linear
const linear = (t) => t
べき乗系(quad / cubic / quart / quint)
最もよく使う。n が大きいほど急峻。
// Quad (n=2)
const easeInQuad = (t) => t * t
const easeOutQuad = (t) => 1 - (1 - t) * (1 - t)
const easeInOutQuad = (t) => t < 0.5
? 2 * t * t
: 1 - Math.pow(-2 * t + 2, 2) / 2
// Cubic (n=3) — UI で最も汎用的
const easeInCubic = (t) => t * t * t
const easeOutCubic = (t) => 1 - Math.pow(1 - t, 3)
const easeInOutCubic = (t) => t < 0.5
? 4 * t * t * t
: 1 - Math.pow(-2 * t + 2, 3) / 2
// Quart (n=4)
const easeInQuart = (t) => t * t * t * t
const easeOutQuart = (t) => 1 - Math.pow(1 - t, 4)
const easeInOutQuart = (t) => t < 0.5
? 8 * t * t * t * t
: 1 - Math.pow(-2 * t + 2, 4) / 2
// Quint (n=5)
const easeInQuint = (t) => t * t * t * t * t
const easeOutQuint = (t) => 1 - Math.pow(1 - t, 5)
const easeInOutQuint = (t) => t < 0.5
? 16 * t * t * t * t * t
: 1 - Math.pow(-2 * t + 2, 5) / 2
sin(最もマイルド)
const easeInSine = (t) => 1 - Math.cos((t * Math.PI) / 2)
const easeOutSine = (t) => Math.sin((t * Math.PI) / 2)
const easeInOutSine = (t) => -(Math.cos(Math.PI * t) - 1) / 2
circ(円弧)
const easeInCirc = (t) => 1 - Math.sqrt(1 - t * t)
const easeOutCirc = (t) => Math.sqrt(1 - Math.pow(t - 1, 2))
const easeInOutCirc = (t) => t < 0.5
? (1 - Math.sqrt(1 - Math.pow(2 * t, 2))) / 2
: (Math.sqrt(1 - Math.pow(-2 * t + 2, 2)) + 1) / 2
expo(指数)
const easeInExpo = (t) => t === 0 ? 0 : Math.pow(2, 10 * t - 10)
const easeOutExpo = (t) => t === 1 ? 1 : 1 - Math.pow(2, -10 * t)
const easeInOutExpo = (t) => {
if (t === 0) return 0
if (t === 1) return 1
return t < 0.5
? Math.pow(2, 20 * t - 10) / 2
: (2 - Math.pow(2, -20 * t + 10)) / 2
}
back(行き過ぎて戻る)
ボタンが押された感、ポップアップの登場で人気。
const c1 = 1.70158
const c2 = c1 * 1.525
const c3 = c1 + 1
const easeInBack = (t) => c3 * t * t * t - c1 * t * t
const easeOutBack = (t) => 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2)
const easeInOutBack = (t) => t < 0.5
? (Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2
: (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2
1.70158 は元論文 Robert Penner の標準値(10% オーバーに対応)。強くしたければ大きく。
elastic(ゴムのように振動)
const c4 = (2 * Math.PI) / 3
const c5 = (2 * Math.PI) / 4.5
const easeInElastic = (t) => {
if (t === 0) return 0
if (t === 1) return 1
return -Math.pow(2, 10 * t - 10) * Math.sin((t * 10 - 10.75) * c4)
}
const easeOutElastic = (t) => {
if (t === 0) return 0
if (t === 1) return 1
return Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1
}
const easeInOutElastic = (t) => {
if (t === 0) return 0
if (t === 1) return 1
return t < 0.5
? -(Math.pow(2, 20 * t - 10) * Math.sin((20 * t - 11.125) * c5)) / 2
: (Math.pow(2, -20 * t + 10) * Math.sin((20 * t - 11.125) * c5)) / 2 + 1
}
bounce(地面に弾む)
function easeOutBounce(t) {
const n1 = 7.5625
const d1 = 2.75
if (t < 1 / d1) {
return n1 * t * t
} else if (t < 2 / d1) {
return n1 * (t -= 1.5 / d1) * t + 0.75
} else if (t < 2.5 / d1) {
return n1 * (t -= 2.25 / d1) * t + 0.9375
} else {
return n1 * (t -= 2.625 / d1) * t + 0.984375
}
}
const easeInBounce = (t) => 1 - easeOutBounce(1 - t)
const easeInOutBounce = (t) => t < 0.5
? (1 - easeOutBounce(1 - 2 * t)) / 2
: (1 + easeOutBounce(2 * t - 1)) / 2
30 種類まとめ(Easings オブジェクト)
export const Easings = {
linear: (t) => t,
easeInQuad, easeOutQuad, easeInOutQuad,
easeInCubic, easeOutCubic, easeInOutCubic,
easeInQuart, easeOutQuart, easeInOutQuart,
easeInQuint, easeOutQuint, easeInOutQuint,
easeInSine, easeOutSine, easeInOutSine,
easeInCirc, easeOutCirc, easeInOutCirc,
easeInExpo, easeOutExpo, easeInOutExpo,
easeInBack, easeOutBack, easeInOutBack,
easeInElastic, easeOutElastic, easeInOutElastic,
easeInBounce, easeOutBounce, easeInOutBounce,
}
逆関数 / 反転
easeIn と easeOut は数学的に対称:
const flip = (t) => 1 - t
const easeOutXxx = (t) => 1 - easeInXxx(flip(t))
独自の easeIn を作れば、easeOut と easeInOut は機械的に作れる:
function makeOut(easeIn) {
return (t) => 1 - easeIn(1 - t)
}
function makeInOut(easeIn) {
return (t) => t < 0.5
? easeIn(2 * t) / 2
: 1 - easeIn(2 * (1 - t)) / 2
}
cubic-bezier(CSS と同じ)
CSS の cubic-bezier(.25, .1, .25, 1) を JS で動かす:
function cubicBezier(x1, y1, x2, y2) {
// Newton-Raphson で x → t を解く
return function(x) {
if (x <= 0) return 0
if (x >= 1) return 1
const sampleX = (t) => 3*(1-t)*(1-t)*t*x1 + 3*(1-t)*t*t*x2 + t*t*t
const sampleY = (t) => 3*(1-t)*(1-t)*t*y1 + 3*(1-t)*t*t*y2 + t*t*t
const dxdt = (t) =>
3*(1-t)*(1-t)*x1 + 6*(1-t)*t*(x2-x1) + 3*t*t*(1-x2)
let t = x
for (let i = 0; i < 8; i++) {
const cur = sampleX(t) - x
if (Math.abs(cur) < 1e-6) break
const d = dxdt(t)
if (Math.abs(d) < 1e-6) break
t -= cur / d
}
return sampleY(t)
}
}
const easeInOut = cubicBezier(0.42, 0, 0.58, 1)
easeInOut(0.5) // ~0.5
step / staircase(離散)
パラパラ漫画的に階段状に進む:
function steps(n, position = "end") {
return (t) => {
if (position === "start") return Math.ceil(t * n) / n
return Math.floor(t * n) / n
}
}
const step3 = steps(3)
step3(0.0) // 0
step3(0.5) // 0.333...
step3(1.0) // 1.0
カスタム合成
2 つのイージングを切り替える / 重ねる:
// 前半 easeIn、後半 easeOut のような組み合わせ
function chain(easeA, easeB, breakpoint = 0.5) {
return (t) => t < breakpoint
? easeA(t / breakpoint) * breakpoint
: breakpoint + easeB((t - breakpoint) / (1 - breakpoint)) * (1 - breakpoint)
}
イージングの選び方
| 感じたい印象 | おすすめ |
|---|---|
| 素早く、上品 | easeOutCubic / easeOutQuart |
| 機械的 | linear(実は不自然なので滅多に使わない) |
| 軽快なポップ | easeOutBack(c1=2 にすると派手) |
| ふわっと | easeOutSine / easeInOutSine |
| 勢いよく入って減速 | easeOutQuart / easeOutExpo |
| ゲームの跳ね返り | easeOutBounce / easeOutElastic |
| 遷移(A → B 滑らか) | easeInOutCubic |
視覚的な目安(横軸 t、縦軸 出力)
linear ▁▂▃▄▅▆▇█
easeIn ▁▁▁▂▃▄▆█
easeOut █▆▄▃▂▁▁▁ ↑反転
easeInOut ▁▁▂▄▆██▇▆
easeOutBack ▁▃▆██▇█ ↑1 を超える
easeOutBounce ▁▃▇▅▇██ ↑何度か跳ねる
WAAPI / CSS との対応
| CSS | 近似 JS |
|---|---|
ease | cubicBezier(0.25, 0.1, 0.25, 1.0) |
ease-in | cubicBezier(0.42, 0, 1, 1) |
ease-out | cubicBezier(0, 0, 0.58, 1) |
ease-in-out | cubicBezier(0.42, 0, 0.58, 1) |
1 を超えるイージング(Anticipation)
easeInBack や easeOutElastic は途中で1 を超えたり、マイナスになる。 これは仕様で、生き物っぽい動きの肝。値の範囲を厳密に縛る場合は注意。
イージングの可視化
function plotEasing(easing, canvas) {
const ctx = canvas.getContext("2d")
const w = canvas.width, h = canvas.height
ctx.clearRect(0, 0, w, h)
ctx.beginPath()
for (let x = 0; x <= w; x++) {
const t = x / w
const y = h - easing(t) * h
if (x === 0) ctx.moveTo(x, y)
else ctx.lineTo(x, y)
}
ctx.stroke()
}
plotEasing(Easings.easeOutBack, canvas)
パフォーマンス
どのイージングも 1 フレームに数十回呼んでも無視できるコスト。気にしなくてよい。 cubicBezier の Newton 法も 8 反復程度なので速い。
参考実装と検証
- easings.net — 全 30 種をビジュアルで確認
- cubic-bezier.com — ベジェ調整
- easings.co — easings.net 風の比較
- Robert Penner Easing Equations(原典)
UI のほぼすべては easeOutCubic か easeInOutCubic でいける。
演出を強めたいときに back / elastic を足す、というのが定石。