コンテンツにスキップ

Exaggeration(誇張)

現実どおりの動きは画面上では弱く見えます。誇張は動きの本質を強調するための道具であり、UI ではブランドの性格に合わせて振れ幅を上げ下げするつまみとして使います。

LIVE
スライダーで倍率(0.3〜3)を選び、ボタンで jump() を再生する。1 未満で控えめに、1 より大きいと高く跳んで深く潰れる。

誇張は、現実の動きや形を強めて、観客に意図を確実に伝える技法です。驚いた顔は目を実際より大きく見開き、重い荷物を持ち上げる動作は体を大きく反らせます。現実どおりに描いた動きは、画面上では単調で退屈に見えがちです。

誇張は「何でも大きくする」ことではありません。その場面で伝えたい一点(驚き、重さ、勢い)を選び、そこだけを強めます。すべてを誇張すると、どこを見ればよいかがかえってわからなくなります。

誇張は他の原則の強さを決めるつまみでもあります。潰れと伸び(Squash & Stretch)を深くする、溜め(Anticipation)を大きくする、軌道の弧(Arcs)を膨らませる、といった形で現れます。

UI の誇張は、一つのモーションの「振れ幅」を倍率で調整することです。本ライブラリでは、静止状態(REST)からの偏差に倍率を掛ける演算として実装しています。

  • factor > 1 で強調、factor < 1 で抑制、factor = 0 で静止状態、負の値で反転します。反転は Anticipation の溜めポーズの作り方そのものです。
  • 既定では opacity を対象から外します。見え方(透明度)の変化は誇張すべき「動き」ではないためです。
  • 尺(duration)やイージングは変えません。同じ時間で移動量が増えるので、倍率を上げると速度も上がります。

用途ごとの目安は次のとおりです。

用途 倍率の目安 理由
管理画面・データの多い画面 0.5〜0.8 動きを情報より目立たせない
一般的なボタン・カード 0.9〜1.1 既定値のまま
達成・成功・オンボーディング 1.3〜2 一度きりの場面で感情を伝える。bouncy 個性は 1.7
ゲーム的な演出 2 以上 常用すると疲れる。cartoon 個性は 2.4

Personality の exaggerationnatural 1、snappy 0.9、calm 0.7、playful 1.4、bouncy 1.7、cartoon 2.4)は、この倍率をプロダクト全体で揃えるための値です。レシピと振る舞いは次の量にこの値を掛けます。

対象 既定の量 calm(0.7) playful(1.4) bouncy(1.7) cartoon(2.4)
enter / exit の移動距離 12px 8.4px 16.8px 20.4px 28.8px
enter("pop") の開始スケール 0.85 0.895 0.79 0.745 0.64
jump の高さ 16px 11.2px 22.4px 27.2px 38.4px
shake の振幅 6px 4.2px 8.4px 10.2px 14.4px
hoverable の浮上量 level 6 4.2 8.4 10.2 14.4
tiltable の最大角度 8deg 5.6deg 11.2deg 13.6deg 19.2deg

派手な動き のレシピ(rubberBand / jello / tada / heartbeat / swing / wobble / flip / bounceIn / fallIn)も、振れ幅を exaggeration に比例させています。たとえば swing の最大角度は natural で 15deg、cartoon で 36deg です。ただし要素が壊れないよう、flip の手前への浮き上がりは倍率 2、bounceIn の開始スケールは 0.05、rubberBand の潰れは -0.9 で頭打ちになります。

レシピは exaggerate を内部で使っているので、入場時の opacity: 0 は Personality に関係なく 0 のままです。

function exaggerate(target: MotionSpec, factor: number, options?: ExaggerateOptions): MotionSpec;
function exaggerate<P extends Pose>(target: P, factor: number, options?: ExaggerateOptions): P;

targetframes があれば MotionSpec として全フレームを、なければ Pose として 1 つのポーズを変換します。各キーは rest + (value - rest) * factor になります。factor が有限数でなければ RangeError を投げます。

オプション 既定値 説明
rest Pose REST 偏差を測る基準ポーズ。指定しないキーは REST の値を使う
keys readonly PoseKey[] opacity 以外の全キー 倍率を掛けるキー

MotionSpec を渡した場合、durationdelayeasingorigin などのフィールドと、各フレームの offseteasing はそのまま残ります。ポーズに存在しないキーは追加されません。

exaggerate({ y: -10, scale: 1.1, opacity: 0.5 }, 2);
// { y: -20, scale: 1.2, opacity: 0.5 } opacity は既定で対象外
exaggerate({ y: -10, scale: 1.1 }, -0.5);
// { y: 5, scale: 0.95 } 反転して半分
exaggerate({ x: 110 }, 2, { rest: { x: 100 } });
// { x: 120 } 基準 100 からの偏差 10 を 2 倍
function scaleDeviation<P extends Pose>(
pose: P,
factor: number,
rest?: Pose, // 既定 REST
keys?: readonly PoseKey[], // 既定 opacity 以外の全キー
): P;

exaggerate がポーズ 1 つに対して使う下位関数です。オプションオブジェクトではなく位置引数を取ります。factor の検証はしません。

結果は次の値にクランプされます。

キー クランプ
opacity 0〜1
blur 0 以上
elevation 0 以上

scale / scaleX / scaleY はクランプされません(落とし穴を参照)。

const REST: Readonly<Required<Pose>>;
// x, y, z, rotate, rotateX, rotateY, skewX, skewY, blur, elevation は 0
// scale, scaleX, scaleY, opacity は 1

同じ jump でも、通知バッジでは控えめに、初めての実績解除では大きく跳ばせます。

import { enter, exaggerate, jump, play } from "twelve-principles";
// 通常の通知は控えめに
export function nudge(badge: HTMLElement): void {
play(badge, exaggerate(jump(), 0.6));
}
// 初めての実績解除だけは大きく
export function celebrate(trophy: HTMLElement): void {
play(trophy, exaggerate(jump({ height: 20 }), 1.8));
}
// 密度の高い画面では pop の縮小を半分に(開始スケール 0.85 → 0.925)
export function showRow(row: HTMLElement): void {
play(row, exaggerate(enter("pop"), 0.5));
}

ポーズを返す関数(lifttiltTowardsquash など)の結果にも使えます。

import { exaggerate, hoverable, lift } from "twelve-principles";
// lift(4) = { y: -2, elevation: 4 } → { y: -4, elevation: 8 }
hoverable(featuredCard, { level: 0, pose: exaggerate(lift(4), 2) });

hoverableposelift(level * exaggeration) の上に展開されるため、level: 0 にしておくと pose の値だけが使われます。

  • Squash & Stretchexaggerate(squashStretch(), 2) で衝撃の潰れを深くします。
  • AnticipationwindUp は負の倍率の scaleDeviation で溜めのポーズを作ります。反転と誇張は同じ演算です。
  • Follow ThroughfollowThrough を適用したあとに exaggerate しても、スプリングのイージングはフレームに残ります。
  • Timing — 振れ幅を変えたら尺も見直します。
  • Appeal — Personality の exaggeration が、すべてのレシピと振る舞いの基準倍率になります。
  • 派手な動きexaggerate(tada(), 1.5) のように、派手なレシピにさらに倍率を掛けられます。cartoon(2.4)の振れ幅にさらに大きな倍率を掛けると、スケールが 0 を下回ることがあるので結果を確認してください。