API リファレンス
twelve-principles のエントリポイント(src/index.ts)からエクスポートされるものを、役割ごとにまとめています。React バインディング(twelve-principles/react)は末尾の一覧と React ガイド を参照してください。
import { play, enter, pressable } from "twelve-principles";interface Pose { x?: number; y?: number; z?: number; scale?: number; scaleX?: number; scaleY?: number; rotate?: number; rotateX?: number; rotateY?: number; skewX?: number; skewY?: number; opacity?: number; blur?: number; elevation?: number;}
type PoseKey = keyof Pose;要素の見た目の状態を、無変形の静止状態に対する絶対値 で表します。{ x: 10 } は「今の位置から +10px」ではなく「静止位置から 10px」です。省略したキーは静止値(REST)として扱われます。
| キー | 単位 | 静止値 | CSS への変換 |
|---|---|---|---|
x / y / z |
px | 0 | translate3d() |
scale / scaleX / scaleY |
倍率 | 1 | scale(scale × scaleX, scale × scaleY) |
rotate / rotateX / rotateY |
deg | 0 | rotate() / rotateX() / rotateY() |
skewX / skewY |
deg | 0 | skew() |
opacity |
0〜1 | 1 | opacity |
blur |
px | 0 | filter: blur() |
elevation |
dp(0 = 面に接地、24 前後 = 高く浮く) | 0 | box-shadow(shadowForElevation) |
PoseFrame
Section titled “PoseFrame”interface PoseFrame extends Pose { offset?: number; easing?: Easing;}タイムライン上に置いたポーズです。offset は 0〜1 の位置で、省略すると WAAPI と同じ規則で配置されます(両端は 0 と 1、間は均等)。easing は このフレームから始まる区間 に適用されます。
MotionSpec
Section titled “MotionSpec”interface MotionSpec { readonly frames: readonly PoseFrame[]; readonly duration: number; readonly delay?: number; readonly easing?: Easing; readonly iterations?: number; readonly direction?: PlaybackDirection; readonly origin?: string; readonly perspective?: number;}1 つのモーションを表す、フレームワークに依存しない宣言的なデータです。
| フィールド | 型 | 既定値 | 説明 |
|---|---|---|---|
frames |
readonly PoseFrame[] |
(必須) | 1 枚以上のフレーム |
duration |
number |
(必須) | 1 イテレーションの尺(ms)。0 以上 |
delay |
number |
0 |
開始までの遅延(ms) |
easing |
Easing |
"linear" |
イテレーション全体に掛かるイージング(WAAPI の options.easing) |
iterations |
number |
1 |
繰り返し回数。Infinity で無限ループ |
direction |
PlaybackDirection |
"normal" |
"normal" / "reverse" / "alternate" / "alternate-reverse" |
origin |
string |
CSS transform-origin(例: 床に向かって潰すなら "50% 100%") |
|
perspective |
number |
指定すると transform の先頭に perspective(px) を加え、rotateX / rotateY / z を立体的に描く |
Easing と EasingFn
Section titled “Easing と EasingFn”type EasingFn = (t: number) => number;type Easing = string | EasingFn;Easing は CSS のイージング文字列("linear"、"ease-out"、"cubic-bezier(...)")か、JS 関数(スプリングや独自の曲線)です。EasingFn は時間の進み具合 0〜1 を補間の進み具合に写し、0〜1 の外に出てもかまいません(オーバーシュート)。JS 関数はコンパイル時にキーフレームへ焼き込まれるので、どの WAAPI 実装でも動きます。
interface Point { x: number; y: number;}arcPoint と tiltToward が使う 2 次元の点です。
コア: ポーズ
Section titled “コア: ポーズ”const REST: Readonly<Required<Pose>>;すべてのポーズの基準となる静止状態です(x / y / z / 回転 / skew / blur / elevation は 0、scale / scaleX / scaleY / opacity は 1)。凍結済みのオブジェクトです。
lerpPose
Section titled “lerpPose”function lerpPose(a: Pose, b: Pose, t: number): Pose;2 つのポーズを線形補間します。どちらかにあるキーをすべて対象にし、ない側は REST の値とみなします。
isRestPose
Section titled “isRestPose”function isRestPose(pose: Pose): boolean;ポーズが無変形の要素と見た目上同一(全キーが REST との差 1e-6 未満)なら true を返します。
scaleDeviation
Section titled “scaleDeviation”function scaleDeviation<P extends Pose>(pose: P, factor: number, rest?: Pose, keys?: readonly PoseKey[]): P;pose の rest(既定 REST)からの偏差を factor 倍します。factor > 1 で遠ざけ、factor < 1 で近づけ、負数で反転します(反転したポーズがそのまま Anticipation の溜めになります)。keys の既定は opacity 以外の全キーです。結果は opacity を 0〜1、blur と elevation を 0 以上にクランプします。
composePoses
Section titled “composePoses”function composePoses(layers: readonly Pose[]): Pose;独立したレイヤー(ホバー + 押下 + チルトなど)を 1 つのポーズに合成します。平行移動・回転・skew・blur・elevation は 加算、scale / scaleX / scaleY / opacity は 乗算 です。結果はクランプされます。
shadowForElevation
Section titled “shadowForElevation”function shadowForElevation(elevation: number): string;画面の上にある 1 つの光源を前提に、elevation から 2 層の box-shadow を作ります。高く浮くほど影は遠く・大きく・薄くなります。常に 2 層なので値が滑らかに補間されます。
shadowForElevation(6);// "0px 3px 6px rgba(0, 0, 0, 0.210), 0px 0.9px 15px rgba(0, 0, 0, 0.100)"poseToStyle
Section titled “poseToStyle”function poseToStyle(pose: Pose, keys: readonly PoseKey[], perspective?: number): PoseStyle;
interface PoseStyle { transform?: string; opacity?: string; filter?: string; boxShadow?: string;}ポーズを CSS に変換します。keys で transform 関数の並びを固定するので、1 つのアニメーションの全キーフレームが同じ構造になり、transform リストが正しく補間されます。関数の順序は perspective()、translate3d()、rotateX()、rotateY()、rotate()、skew()、scale() です。
コア: spec
Section titled “コア: spec”compile
Section titled “compile”function compile(spec: MotionSpec): CompiledMotion;
interface CompiledMotion { keyframes: Keyframe[]; options: KeyframeAnimationOptions;}MotionSpec を element.animate() の引数に変換する純関数です。CSS のイージング文字列はそのまま渡し、JS のイージングを含む場合は密な線形キーフレームに焼き込みます(実行時の振る舞い)。options は delay 0、iterations 1、direction "normal"、fill "both" を既定とします。フレームが 0 枚、または duration が負の場合は RangeError です。
frameOffsets
Section titled “frameOffsets”function frameOffsets(frames: readonly PoseFrame[]): number[];省略された offset を WAAPI と同じ規則で解決します(両端を 0 / 1 に固定し、間を均等に配置)。フレームが 1 枚なら [offset ?? 1] です。0〜1 の範囲外や降順があると RangeError です。
sampleSpec
Section titled “sampleSpec”function sampleSpec(spec: MotionSpec, t: number): Pose;1 イテレーション中の時間の進み具合 t(0〜1 にクランプ)でのポーズを、ブラウザのキーフレーム補間と同じ方法(全体のイージング → 区間のイージング → 線形補間)で求めます。モーションを途中で中断して滑らかに繋ぐために使われます。
withoutGlobalEasing
Section titled “withoutGlobalEasing”function withoutGlobalEasing(spec: MotionSpec): MotionSpec;spec 全体の easing を各フレームの区間イージングへ移し替えます。キーポーズは残し、offset と区間イージングだけが変わります。フレームを挿入・差し替える前(anticipate、followThrough)に必要な変換です。全体のイージングが単調でない(行き過ぎる)場合は、60fps 相当で最低 12 枚のフレームに再サンプリングします。
コア: イージング
Section titled “コア: イージング”easings
Section titled “easings”const easings: { readonly linear: "linear"; readonly inOut: "cubic-bezier(0.65, 0, 0.35, 1)"; readonly out: "cubic-bezier(0.22, 1, 0.36, 1)"; readonly in: "cubic-bezier(0.55, 0, 1, 0.45)"; readonly anticipate: "cubic-bezier(0.36, 0, 0.66, -0.56)"; readonly overshoot: "cubic-bezier(0.34, 1.56, 0.64, 1)";};
type EasingName = keyof typeof easings;| 名前 | 用途 |
|---|---|
linear |
プログレスバー・スピナー・スクラブのみ。空間的な動きには使わない |
inOut |
画面内の 2 状態間の移動 |
out |
入場(素早く到着し、ゆっくり落ち着く) |
in |
退場(勢いをつけて去る) |
anticipate |
一度後ろに下がってから進む |
overshoot |
行き過ぎて戻る |
cubicBezier
Section titled “cubicBezier”function cubicBezier(x1: number, y1: number, x2: number, y2: number): EasingFn;CSS の cubic-bezier() と同じ曲線を JS 関数として返します。x1 / x2 が 0〜1 の外なら RangeError です。
resolveEasing
Section titled “resolveEasing”function resolveEasing(easing: Easing | undefined): EasingFn;任意の Easing を JS 関数に解決します。undefined と "linear" は恒等関数、"ease" / "ease-in" / "ease-out" / "ease-in-out" と cubic-bezier() は対応する曲線、関数はそのまま返します。それ以外(steps()、CSS の linear() など)は TypeError です。文字列ごとにキャッシュされます。
コア: スプリング
Section titled “コア: スプリング”spring
Section titled “spring”function spring(options?: SpringOptions): Spring;
interface Spring extends EasingFn { readonly duration: number; readonly stiffness: number; readonly damping: number; readonly mass: number;}減衰調和振動子のイージングを返します。行き過ぎて落ち着く動き(Follow through、Exaggeration)にはベジェの代わりにこれを使います。戻り値は 0〜1 を受け取る関数で、.duration に 静定時間(値が目標の 0.1% 以内に収まり続けるまでの ms)を持ちます。animateTo にスプリングを渡すと、この .duration がそのまま尺になります。
SpringOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
duration |
number |
400 |
知覚的な尺(ms)。減衰なしの振動の周期 |
bounce |
number |
0.2 |
0 = 臨界減衰(行き過ぎなし)、0.3 = 生き生き、0.6 以上 = カートゥーン。0〜0.95 にクランプ |
stiffness |
number |
170(物理指定時) |
バネ定数 |
damping |
number |
26(物理指定時) |
減衰係数 |
mass |
number |
1 |
質量 |
velocity |
number |
0 |
初速(1 = 1 秒で全距離) |
stiffness か damping のどちらかを指定すると物理パラメータとして扱い、duration / bounce は無視されます。質量・剛性・減衰が正の有限値にならない場合(duration: 0 など)は RangeError です。spring() の既定値での .duration は 542ms です。
isSpring
Section titled “isSpring”function isSpring(easing: unknown): easing is Spring;数値の duration プロパティを持つ関数なら true を返します。
コア: タイミング
Section titled “コア: タイミング”durations と DurationToken
Section titled “durations と DurationToken”const durations: { readonly instant: 100; readonly fast: 150; readonly base: 200; readonly slow: 300; readonly deliberate: 500;};
type DurationToken = keyof typeof durations;deliberate はシステム起点の演出(オンボーディング、空状態)専用です。
USER_INITIATED_MAX_MS と MAX_STAGGER_MS
Section titled “USER_INITIATED_MAX_MS と MAX_STAGGER_MS”const USER_INITIATED_MAX_MS = 300;const MAX_STAGGER_MS = 50;ユーザー起点のモーションの尺の上限の目安と、スタッガーの 1 項目あたりの遅延の上限の目安です。MAX_STAGGER_MS は StaggerOptions.cap の既定値として使われます(cap を指定すれば超えられます)。
duration
Section titled “duration”function duration(value: DurationToken | number, tempo?: number): number; // tempo 既定 1トークンまたは ms に tempo を掛けて Math.round します。値が負、または tempo が 0 以下なら RangeError です。
travelDuration
Section titled “travelDuration”function travelDuration(distancePx: number, options?: TravelOptions): number;TravelOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
min |
number |
150 |
距離 0 のときの尺(ms) |
max |
number |
300(USER_INITIATED_MAX_MS) |
最大の尺(ms) |
reference |
number |
800 |
max に達する距離(px) |
距離の平方根に比例して min から max へ補間します。長い移動が間延びせず、短い移動がせわしなくなりません。100px で 203ms、400px で 256ms です。
staggerDistances
Section titled “staggerDistances”function staggerDistances(count: number, from?: "first" | "last" | "center" | number): number[];各項目がカスケードの起点から何ステップ離れているかを返します(0 = 最初に動く)。"center" は (count - 1) / 2 を起点にするので、偶数個では 0.5 刻みになります。count が 0 以上の整数でなければ RangeError です。
staggerDelays
Section titled “staggerDelays”function staggerDelays(count: number, options?: StaggerOptions): number[];StaggerOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
each |
number |
30 |
隣同士の遅延(ms)。cap でクランプ |
cap |
number |
MAX_STAGGER_MS(50) |
1 項目あたりの遅延の上限(ms)。負または NaN なら RangeError |
total |
number |
300 |
カスケード全体の上限(ms) |
from |
"first" | "last" | "center" | number |
"first" |
起点。数値ならそのインデックス |
1 ステップの遅延は Math.min(each, cap, total / 最大距離) で、各項目の遅延は丸められます。staggerDelays(5) は [0, 30, 60, 90, 120]、staggerDelays(6, { each: 120, cap: 150, total: 900 }) は [0, 120, 240, 360, 480, 600] です。
各原則の背景と使い方は、それぞれの原則ページを参照してください。原則 6(Slow in & slow out)は コア: イージング、原則 9(Timing)は コア: タイミング の API で表現されます。
1. Squash & Stretch
Section titled “1. Squash & Stretch”type Axis = "x" | "y";
function deform(amount: number, axis?: Axis): Pose; // axis 既定 "y"function squash(amount?: number, axis?: Axis): Pose; // amount 既定 0.05function stretch(amount?: number, axis?: Axis): Pose; // amount 既定 0.05function squashStretch(options?: ImpactOptions): MotionSpec;deform: 体積を保つ(scaleX × scaleY = 1)変形ポーズ。amount > 0でaxis方向に伸び、< 0で潰れます。amountが -1 以下ならRangeError。squash/stretch:amountの絶対値で潰す / 伸ばすdeformの短縮形。squashStretch: 衝撃への反応。offset 0.25 でsquash(intensity)、0.55 でstretch(intensity × 0.5)、0.8 でsquash(intensity × 0.15)、1 で静止。反発のたびに弱くなることで重さが伝わります。
ImpactOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
intensity |
number |
0.05 |
最大の変形量。UI は 0.03〜0.05、キャラクターは 0.2 以上 |
axis |
Axis |
"y" |
衝撃の向き |
duration |
number |
300 |
尺(ms) |
origin |
string |
"y" なら "50% 100%"、"x" なら "0% 50%" |
面に接する位置 |
詳細: Squash & Stretch
2. Anticipation
Section titled “2. Anticipation”function anticipate(spec: MotionSpec, options?: AnticipationOptions): MotionSpec;function windUp(from: Pose, to: Pose, amount?: number): Pose; // amount 既定 0.15anticipate: 最初の区間の前に逆方向の溜めフレームを挿入します。溜めはwindUp(frames[0], frames[1], amount)で、offsetshareに置かれ、元の動作は残りの[share, 1]に詰め直されます。amountが 0、またはフレームが 2 枚未満なら spec をそのまま返します。windUp:from → toに対する溜めポーズ。空間的なキーだけを反転し、opacity はfromの値のまま(見え方は引き戻さない)です。
AnticipationOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
amount |
number |
0.15 |
溜めの大きさ(最初の移動に対する割合) |
share |
number |
0.3 |
タイムラインのうち溜めに使う割合。0〜1 の開区間でなければ RangeError |
fit |
"compress" | "extend" |
"compress" |
"compress" は総尺を維持、"extend" は元の動作の尺を維持して総尺を duration / (1 - share) に延ばす |
詳細: Anticipation
3. Staging
Section titled “3. Staging”function stagingPoses(options?: StagingOptions): StagingPoses;
interface StagingPoses { focus: Pose; surroundings: Pose;}主役を前に出し、周囲を下げるポーズの組を返します。focus は { scale: 1 + recede, elevation: lift }、surroundings は { opacity: 1 - dim, blur, scale: 1 - recede } です。DOM に適用するのは stage です。
StagingOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
dim |
number |
0.5 |
周囲の暗さ(0〜1)。範囲外は RangeError |
blur |
number |
2 |
周囲のぼかし(px) |
recede |
number |
0.02 |
周囲が縮み、主役が拡大する割合 |
lift |
number |
12 |
主役の elevation |
詳細: Staging
4. Straight Ahead & Pose to Pose
Section titled “4. Straight Ahead & Pose to Pose”function poseToPose(keys: readonly PoseFrame[], options?: TimelineOptions): MotionSpec;function straightAhead(draw: (t: number, elapsedMs: number) => Pose, options: StraightAheadOptions): MotionSpec;poseToPose: キーポーズを並べ、中割りをブラウザに任せます。キーは 2 つ以上(未満ならRangeError)で、offsetの順序は作成時に検証されます。easingのない区間(最後のフレーム以外)にはeasings.inOutが入ります。straightAhead:draw(t, elapsedMs)をfpsでサンプリングし、round(duration / 1000 × fps) + 1枚のフレームにします。重力や揺らぎなど、キーポーズでは表せない動きに使います。durationとfpsが正でなければRangeErrorです。
TimelineOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
duration |
number |
300(durations.slow) |
尺(ms) |
delay |
number |
遅延(ms) | |
easing |
Easing |
タイムライン全体のイージング。省略すると各キーで slow in / slow out | |
iterations |
number |
繰り返し回数 | |
direction |
PlaybackDirection |
再生方向 | |
origin |
string |
transform-origin |
|
perspective |
number |
perspective() の距離(px) |
StraightAheadOptions は TimelineOptions から easing を除き、duration: number(必須)と fps?: number(既定 60)を持ちます。
詳細: Straight Ahead & Pose to Pose
5. Follow Through & Overlapping Action
Section titled “5. Follow Through & Overlapping Action”function followThrough(spec: MotionSpec, options?: FollowThroughOptions): MotionSpec;function overlap(spec: MotionSpec, count: number, options?: OverlapOptions): MotionSpec[];followThrough: 最終区間のイージングをspring({ bounce })に差し替え、終端を行き過ぎてから落ち着かせます。事前にwithoutGlobalEasingを適用します。フレームが 2 枚未満、またはbounceが 0 以下なら spec をそのまま返します。overlap: spec をcount個に複製し、staggerDelaysの遅延を元のdelayに加えます。起点からnステップ後ろのパートにはbounce = drag × n(上限 0.8)のfollowThroughを掛け、後続ほど「軽く」大きく揺れるようにします。
FollowThroughOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
bounce |
number |
0.3 |
行き過ぎの大きさ。0 = なし、0.3 = 生き生き、0.6 = ゆるい |
OverlapOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
drag |
number |
0.1 |
1 ステップあたりの follow through の増分 |
each / cap / total / from |
30 / 50 / 300 / "first" |
StaggerOptions と同じ |
詳細: Follow Through & Overlapping Action
7. Arcs
Section titled “7. Arcs”function arc(from: Pose, to: Pose, options?: ArcOptions): MotionSpec;function arcPoint(a: Point, b: Point, bend: number, t: number): Point;arc:fromとtoのx/yを結ぶ二次ベジェ曲線をsamples + 1枚のフレームでサンプリングします。ほかのキーは並行して線形補間されます。arcPoint: 同じ曲線上の、パラメータtの点を返します。
ArcOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
bend |
number |
0.2 |
直線からの最大のふくらみ(移動距離に対する割合)。正で画面の上側、負で下側 |
orient |
boolean |
false |
進行方向(接線)に合わせて rotate する |
samples |
number |
24 |
経路の分割数 |
duration |
number |
travelDuration(移動距離) |
尺(ms) |
delay |
number |
遅延(ms) | |
easing |
Easing |
easings.inOut |
経路上の進み方 |
詳細: Arcs
8. Secondary Action
Section titled “8. Secondary Action”type SecondaryKind = "wiggle" | "pulse" | "float" | "sway";
function secondaryAction(kind: SecondaryKind, options?: SecondaryOptions): MotionSpec;function accompany(primary: MotionSpec, kind: SecondaryKind, options?: AccompanyOptions): MotionSpec;kind |
フレーム(a = amplitude) |
|---|---|
wiggle |
rotate: 0, -10a, 8a, -5a, 2a, 0(減衰する揺れ) |
pulse |
scale: 1, 1 + 0.08a, 1 |
float |
y: 0, -4a, 0 |
sway |
rotate: 0, 3a, -3a, 0 |
各区間は easings.inOut です。
SecondaryOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
amplitude |
number |
1 |
プリセットに対する強さ。主動作を食わないよう 1 未満を推奨 |
duration |
number |
500(durations.deliberate) |
尺(ms) |
delay |
number |
遅延(ms) | |
iterations |
number |
繰り返し回数 |
accompany は主動作の中に収まる副次動作を作ります。amplitude は attenuation、尺は primary.duration × (1 - lag)、遅延は primary.delay + primary.duration × lag です。
AccompanyOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
attenuation |
number |
0.6 |
プリセットに対する強さ |
lag |
number |
0.15 |
主動作のこの割合だけ遅れて始める |
詳細: Secondary Action
10. Exaggeration
Section titled “10. Exaggeration”function exaggerate(target: MotionSpec, factor: number, options?: ExaggerateOptions): MotionSpec;function exaggerate<P extends Pose>(target: P, factor: number, options?: ExaggerateOptions): P;ポーズ、または spec の全フレームの、rest からの偏差を factor 倍します(scaleDeviation)。offset と easing はそのまま残ります。factor が有限でなければ RangeError です。
ExaggerateOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
rest |
Pose |
REST |
偏差の基準となる中立ポーズ |
keys |
readonly PoseKey[] |
opacity 以外の全キー | 拡大するキー |
詳細: Exaggeration
11. Solid Drawing
Section titled “11. Solid Drawing”function lift(level?: number): Pose; // level 既定 4function tiltToward(point: Point, maxDeg?: number): Pose; // maxDeg 既定 8function solid(spec: MotionSpec, perspective?: number): MotionSpec; // perspective 既定 800lift:{ y: -level × 0.5, elevation: level }。視点側へ浮き、影が大きく・柔らかく・薄くなります。tiltToward: 面をpointへ向ける回転。pointは要素に正規化した座標(左上(-0.5, -0.5)、右下(0.5, 0.5)、範囲外はクランプ)で、rotateX = -y × 2 × maxDeg、rotateY = x × 2 × maxDegです。solid: spec にperspectiveを設定します。0 以下ならRangeErrorです。
詳細: Solid Drawing
12. Appeal
Section titled “12. Appeal”interface Personality { readonly name: string; readonly tempo: number; readonly exaggeration: number; readonly bounce: number; readonly anticipation: number; readonly squash: number; readonly guardrails: boolean;}
type PersonalityName = "natural" | "snappy" | "calm" | "playful" | "bouncy" | "cartoon";type PersonalityInput = PersonalityName | Personality;
// 実際の型は `as const satisfies Record<string, Personality>` のリテラル型const personalities: { readonly [K in PersonalityName]: Personality };function definePersonality(overrides: Partial<Personality>, base?: PersonalityInput): Personality; // base 既定 "natural"function resolvePersonality(input?: PersonalityInput): Personality; // input 既定 "natural"| フィールド | 意味(対応する原則) | 許容範囲 |
|---|---|---|
tempo |
尺の倍率(Timing)。1 未満で機敏、1 超で落ち着く | 0.1〜4 |
exaggeration |
距離・角度・拡大率の倍率(Exaggeration) | 0〜4 |
bounce |
静定時のスプリングの弾み(Follow through) | 0〜0.95 |
anticipation |
溜めの大きさ(Anticipation) | 0〜1 |
squash |
潰し・伸ばしの強さ(0.05 = ±5%) | 0〜0.5 |
guardrails |
true で押下などのフィードバックを UI ガイドライン内に収める。false で上限を外す(現在は pressDepth の上限 0.05 → 0.2) |
boolean |
personalities |
tempo | exaggeration | bounce | anticipation | squash | guardrails |
|---|---|---|---|---|---|---|
natural |
1 | 1 | 0.2 | 0.15 | 0.04 | true |
snappy |
0.8 | 0.9 | 0.1 | 0.08 | 0.03 | true |
calm |
1.25 | 0.7 | 0 | 0 | 0.02 | true |
playful |
1 | 1.4 | 0.45 | 0.25 | 0.08 | true |
bouncy |
0.95 | 1.7 | 0.55 | 0.3 | 0.12 | false |
cartoon |
1.15 | 2.4 | 0.65 | 0.45 | 0.22 | false |
definePersonality:baseを解決してoverridesを重ね、各値を上表の範囲で検証し(数値の範囲外はRangeError、guardrailsが boolean でなければTypeError)、凍結したオブジェクトを返します。nameとguardrailsは省略するとbaseから引き継がれます。resolvePersonality: 名前ならpersonalitiesから引き(未知の名前はRangeError)、オブジェクトならそのまま返します(検証しません)。
詳細: Appeal
type TransitionKind = "fade" | "rise" | "drop" | "slideLeft" | "slideRight" | "zoom" | "pop";
function enter(kind?: TransitionKind, options?: RecipeOptions): MotionSpec; // kind 既定 "rise"function exit(kind?: TransitionKind, options?: RecipeOptions): MotionSpec; // kind 既定 "fade"function jump(options?: JumpOptions): MotionSpec;function shake(options?: RecipeOptions): MotionSpec;function pressDepth(personality?: PersonalityInput): number;function settleSpring(personality?: PersonalityInput): Spring;RecipeOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
personality |
PersonalityInput |
"natural" |
テンポ・誇張・弾性 |
duration |
number |
レシピごと | 尺(ms)。指定すると tempo を掛けない |
distance |
number |
enter / exit は 12、shake は 6 |
移動距離(px)。exaggeration が掛かる |
JumpOptions は RecipeOptions に height?: number(既定 16、exaggeration が掛かる)を加えたものです。
| 関数 | 内容 | 既定の尺 |
|---|---|---|
enter |
隠れたポーズ → 静止、easings.out。pop は followThrough(bounce は max(personality.bounce, 0.25)) |
base 200ms × tempo、pop は slow 300ms × tempo |
exit |
静止 → 隠れたポーズ、easings.in。pop は anticipation > 0 なら anticipate(share 0.35) |
fast 150ms × tempo |
jump |
しゃがみ → 伸びて離陸 → 頂点で滞空 → 着地で潰れ → 静止。origin "50% 100%" |
450ms × tempo |
shake |
x 方向の減衰振動 [0, -1, 0.8, -0.6, 0.4, -0.2, 0] × distance |
400ms × tempo |
pressDepth |
squash × 0.75 を、guardrails が true なら 0.02〜0.05、false なら 0.02〜0.2 にクランプした押下の縮小量 |
|
settleSpring |
spring({ duration: 350 × tempo, bounce })。押下・ホバー・チルトの解除用 |
隠れたポーズの一覧と各レシピの詳細は レシピ を参照してください。
派手なレシピ
Section titled “派手なレシピ”UI ガイドラインの外へ意図して出るための、注意を引く動きと祝福の動きです。尺は基準 × tempo(duration 指定で上書き)、振れ幅は exaggeration に比例し、すべて静止状態で終わります。
function rubberBand(options?: RecipeOptions & { axis?: Axis }): MotionSpec; // axis 既定 "x"function jello(options?: RecipeOptions): MotionSpec;function tada(options?: RecipeOptions): MotionSpec;function heartbeat(options?: RecipeOptions): MotionSpec;function swing(options?: RecipeOptions): MotionSpec;function wobble(options?: RecipeOptions): MotionSpec;function flip(options?: RecipeOptions): MotionSpec;function bounceIn(options?: RecipeOptions): MotionSpec;function fallIn(options?: FallInOptions): MotionSpec;
interface FallInOptions extends RecipeOptions { height?: number; // 既定 80(px)。exaggeration が掛かる}
type ExpressiveRecipe = | "rubberBand" | "jello" | "tada" | "heartbeat" | "swing" | "wobble" | "flip" | "bounceIn" | "fallIn";
const expressive: Record<ExpressiveRecipe, (options?: RecipeOptions) => MotionSpec>;| 関数 | 内容 | 既定の尺 |
|---|---|---|
rubberBand |
体積を保つ deform の減衰振動 [0, 1, -0.8, 0.5, -0.2, 0.1, 0] × 0.25 × exaggeration(-0.9 で下限) |
800ms × tempo |
jello |
skewX / skewY の減衰振動 [0, -1, 0.5, -0.25, 0.125, -0.0625, 0] × 12.5 × exaggeration deg |
900ms × tempo |
tada |
縮んで逆に傾く溜め(〜20%)→ 膨らんで左右に揺れる(30〜90%)→ 静止。拡縮 0.1 × exaggeration、傾き 3 × exaggeration deg |
1000ms × tempo |
heartbeat |
14% と 42% で 1 + 0.3 × exaggeration まで膨らみ、70% 以降は静止 |
1300ms × tempo |
swing |
origin "50% 0%" の振り子 [0, 1, -0.66, 0.33, -0.33, 0] × 15 × exaggeration deg |
1000ms × tempo |
wobble |
x と回転の減衰振動 [0, -1, 0.8, -0.6, 0.4, -0.2, 0]。距離 distance(既定 25)× exaggeration、角度 5 × exaggeration deg |
1000ms × tempo |
flip |
perspective 400 で rotateY -360 → 0。z は最大 150 × min(exaggeration, 2)、着地で scale 1 - 0.05 × exaggeration |
1000ms × tempo |
bounceIn |
opacity 0・scale max(1 - 0.5 × exaggeration, 0.05) から spring({ duration: 450 × tempo, bounce: max(bounce, 0.5) }) で到着 |
スプリングの静定時間(natural で 912ms) |
fallIn |
重力 2600 px/s²・反発 0.45 で 3 回跳ねる straightAhead シミュレーション。速度で伸び、着地で潰れる(squash に比例、最大 0.35)。origin "50% 100%" |
物理的な所要時間 × tempo(natural で 617ms) |
expressive は名前からレシピを引く辞書です。使い分けと Personality ごとの尺は 派手な動き を参照してください。
DOM ランタイム
Section titled “DOM ランタイム”実行時の振る舞い
Section titled “実行時の振る舞い”play を中心とする DOM ランタイムには、次の約束があります。
- 1 要素につき 1 モーション。
playは、その要素で再生中のモーションをcancel()してから新しいモーションを始めます。キャンセルされたAnimationのfinishedは reject されます。途切れなく繋ぐには、currentPose(el)を起点にするか、それを内部で行うanimateToを使います。 - 所有するプロパティ。 アニメーション対象の要素では、ライブラリが
transform/transform-origin/opacity/filter/box-shadowを所有します。自前の CSS transform が必要なら、ラッパー要素に分けてください。 - 終了時の後始末(
persist: true、既定)。 終了時に、終端のポーズ(directionとiterationsを考慮)を求めます。- 終端が静止ポーズ(
isRestPose)なら、上記 5 つのプロパティを インラインスタイルから取り除き、アニメーションを解放します。要素はスタイルシートの状態に戻ります。 - そうでなければ
commitStyles()で終端をインラインスタイルに書き込み、アニメーションを解放します。fillで要素を固定し続けないためです。要素が描画されていない(display: noneや DOM から外れている)ためcommitStyles()が失敗した場合は、アニメーションのfillを残して元の状態へ戻らないようにします。
- 終端が静止ポーズ(
persist: false。 終了後にアニメーションを解放するだけで、インラインスタイルは書き込みません。要素は再生前の見た目に戻るので、「行って戻る」演出に使います。- JS イージングの焼き込み。 CSS のイージング文字列はそのまま WAAPI に渡します。spec の
easingかいずれかのフレームのeasingが JS 関数(スプリング、cubicBezierなど)なら、spec 全体を 60fps 相当でサンプリングし、線形のキーフレームに焼き込みます。区間数はround(duration / 1000 × 60)を 12〜120 にクランプした値で、キーフレームはその +1 枚です(300ms なら 19 枚)。CSS のlinear()関数には依存しません。 - reduced motion は再生前に適用。
reducedMotionが"fade"/"skip"に解決されると、spec はreduceSpecで置き換えられてからコンパイルされます。 - 必要なブラウザ機能。 WAAPI と
Animation.prototype.commitStyles()(Chrome 84+、Firefox 75+、Safari 13.1+)。
function play(element: Element, spec: MotionSpec, options?: PlayOptions): Animation;
type ReducedMotion = "auto" | "fade" | "skip" | "full";WAAPI でモーションを再生し、Animation を返します。await play(el, spec).finished で終了を待てます。
PlayOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
reducedMotion |
ReducedMotion |
"auto" |
"auto" は prefers-reduced-motion が有効なら "fade"、そうでなければ "full"。詳細は アクセシビリティ |
persist |
boolean |
true |
終了後に終端の状態を保つ。false で元に戻る |
playAll
Section titled “playAll”function playAll(items: readonly PlayAllItem[], options?: PlayOptions): Promise<void>;
interface PlayAllItem { element: Element; spec: MotionSpec;}複数のモーションを同じオプションで同時に再生し、すべてが終わると解決します。途中でキャンセルされたモーションがあっても reject しません。
function stop(element: Element): void;要素で再生中のモーションを、その場の見た目で止めます(commitStyles() してから解放)。止めた時点のポーズは currentPose で読めます。再生中のモーションがなければ何もしません。
currentPose
Section titled “currentPose”function currentPose(element: Element): Pose;要素がいま見せているポーズを返します。再生中なら、経過時間・遅延・イテレーション・再生方向から求めたその瞬間のポーズです。再生中でなければ、ライブラリが最後に止めた(または静止状態以外で終えた)ポーズです。どちらもなければ空のポーズ {}(= REST)です。
reduceSpec
Section titled “reduceSpec”function reduceSpec(spec: MotionSpec, mode: "fade" | "skip"): MotionSpec;空間的な動きを取り除いた spec を返します。フレームは [終端の形状 + 始端の opacity, 終端] の 2 枚で、尺は "fade" なら Math.min(spec.duration, 150)、"skip" なら 0 です。"fade" は delay を保ち、"skip" は 0 にします。origin と perspective は引き継ぎ、easing / iterations / direction は引き継ぎません。
prefersReducedMotion
Section titled “prefersReducedMotion”function prefersReducedMotion(): boolean;matchMedia("(prefers-reduced-motion: reduce)") が一致すれば true です。matchMedia がない環境では false を返します。
animateTo
Section titled “animateTo”function animateTo(element: Element, target: Pose, options?: AnimateToOptions): Animation;
type Transition = { duration?: number; easing?: Easing } | Spring;
interface AnimateToOptions extends PlayOptions { transition?: Transition; perspective?: number; origin?: string;}要素の現在のポーズ(currentPose)から target へ、中断可能に遷移します。再生中でも、その瞬間の見た目から途切れずに繋がります。現在のポーズが使っていて target にないキーは、静止値に戻ります。つまり animateTo(el, {}) は現在の状態から静止状態へ戻す操作です。
AnimateToOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
transition |
Transition |
{}(200ms、easings.inOut) |
尺とイージングの組、またはスプリング。スプリングなら .duration(静定時間)が尺になる |
perspective |
number |
perspective() の距離(px) |
|
origin |
string |
transform-origin |
|
reducedMotion / persist |
"auto" / true |
PlayOptions と同じ |
import { animateTo, spring } from "twelve-principles";
animateTo(el, { x: 120 }, { transition: spring({ bounce: 0.3 }) });animateTo(el, { x: 0 }, { transition: { duration: 150, easing: "ease-out" } });setLayer
Section titled “setLayer”function setLayer( element: Element, name: string, pose: Pose | null, options?: Omit<AnimateToOptions, "perspective">,): Animation;要素の名前付きレイヤーを設定(null なら解除)し、全レイヤーを composePoses で合成したポーズへ animateTo します。独立した振る舞いがそれぞれ自分のレイヤーを持つので、互いを上書きしません。組み込みの振る舞いは "press" / "hover" / "tilt" / "stage" を使います。perspective は setPerspective で要素ごとに設定した値が使われます。
import { setLayer } from "twelve-principles";
setLayer(card, "drag", { rotate: 4, elevation: 8 }); // ホバーや押下と合成されるsetLayer(card, "drag", null); // drag レイヤーだけを外すsetPerspective
Section titled “setPerspective”function setPerspective(element: Element, perspective: number | undefined): void;以後の setLayer の遷移すべてに perspective(px) を付けるよう宣言します(3D チルトや反転)。undefined で解除します。tiltable が内部で使います。
layersOf
Section titled “layersOf”function layersOf(element: Element): ReadonlyMap<string, Pose>;要素の現在のレイヤーのスナップショット(コピー)を返します。
type Cleanup = () => void;
function pressable(element: HTMLElement, options?: PressOptions): Cleanup;function hoverable(element: HTMLElement, options?: HoverOptions): Cleanup;function tiltable(element: HTMLElement, options?: TiltOptions): Cleanup;要素にイベントリスナーを付け、レイヤーで状態を表現します。戻り値の Cleanup を呼ぶと、リスナーを外して要素を静止状態へ戻します。3 つとも共通で personality?: PersonalityInput(既定 "natural")と reducedMotion?: ReducedMotion(既定 "auto")を受け取ります。
pressable
Section titled “pressable”押下で素早く縮み(duration("instant", tempo) = 100ms、easings.out)、離すと settleSpring(personality) で戻ります。"press" レイヤーに { scale: 1 - depth } を設定します。
- 押し込む:
pointerdown(主ボタンのみ)、keydownの Enter / Space(キーリピートは無視) - 戻す:
pointerup/pointercancel/pointerleave、keyupの Enter / Space、blur
PressOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
depth |
number |
pressDepth(personality)(0.02〜0.05、guardrails: false なら 0.02〜0.2) |
押下中の縮小量。明示した値はクランプされない |
hoverable
Section titled “hoverable”マウス・ペンのホバーで視点側へ浮きます(タッチは無視)。"hover" レイヤーに { ...lift(level × exaggeration), ...pose } を設定し、入りは duration("base", tempo)(200ms)の easings.out、出は duration("slow", tempo)(300ms)の easings.inOut です。
HoverOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
level |
number |
6 |
ホバー時の elevation(lift の引数)。exaggeration が掛かる |
pose |
Pose |
ホバー状態に重ねるポーズ(例: { scale: 1.02 }) |
tiltable
Section titled “tiltable”ポインタに追従して面を傾けます(タッチは無視)。pointermove ごとに "tilt" レイヤーへ tiltToward(point, max × exaggeration) を duration("fast", tempo)(150ms)の easings.out で設定し、ポインタが離れると settleSpring(personality) で平らに戻ります。付けている間は setPerspective(element, perspective) が有効になり、クリーンアップで解除されます。
TiltOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
max |
number |
8 |
最大の回転角(deg)。exaggeration が掛かる |
perspective |
number |
800 |
パースペクティブの距離(px) |
function stage(focus: HTMLElement, options?: StageOptions): StageHandle;
interface StageHandle { release(): Promise<void>;}focus を主役として前に出し、周囲を暗く・ぼかし・後退させます。それぞれの要素の "stage" レイヤーに stagingPoses のポーズを duration("slow", tempo)(300ms)の easings.out で設定します。拡大した主役が隣に重なっても上に描かれるよう、適用中は focus のインラインスタイルに z-index: 10 を設定し、position が static なら relative にします。
release() は全要素の "stage" レイヤーを duration("base", tempo)(200ms)の easings.inOut で外し、すべてのモーションが終わると z-index と position を元の値に戻して解決します。2 回目以降の呼び出しは同じ Promise を返します。
StageOptions |
型 | 既定値 | 説明 |
|---|---|---|---|
surroundings |
readonly HTMLElement[] |
focus の兄弟要素 |
後ろに下がる要素 |
dim / blur / recede / lift |
0.5 / 2 / 0.02 / 12 |
StagingOptions と同じ |
|
personality |
PersonalityInput |
"natural" |
尺の tempo |
reducedMotion |
ReducedMotion |
"auto" |
import { stage } from "twelve-principles";
const handle = stage(selectedCard, { dim: 0.6 });// ...await handle.release();React(twelve-principles/react)
Section titled “React(twelve-principles/react)”詳細な仕様と使用例は React ガイド にあります。
| エクスポート | シグネチャ |
|---|---|
MotionProvider |
(props: { personality?: PersonalityInput; reducedMotion?: ReducedMotion; children?: ReactNode }) => JSX.Element |
useMotionConfig |
() => MotionConfig({ personality: Personality; reducedMotion: ReducedMotion }) |
resolveSpec |
(input: SpecInput, personality: Personality) => MotionSpec |
usePress / useHover / useTilt |
(options?: XxxOptions | boolean) => RefCallback<T> |
useEnter |
(input?: EnterInput | false) => RefCallback<T>(既定 "rise") |
usePresence |
(show: boolean, options?: PresenceOptions) => PresenceState<T>({ present, ref }) |
useMotion |
() => MotionControls<T>({ ref, play, animateTo, stop }) |
useStage |
(active: boolean, options?: Omit<StageOptions, "personality" | "reducedMotion">) => RefCallback<T> |
useCascade |
(input?: EnterInput, options?: CascadeOptions) => RefCallback<T>(既定 "rise") |
Motion |
as / press / hover / tilt / enter と HTML 属性を受け取る forwardRef コンポーネント |
Presence |
show / enter / exit / initial / as と HTML 属性を受け取るコンポーネント |
mergeRefs |
(...refs: (Ref<T> | undefined)[]) => RefCallback<T> |
型: SpecInput(MotionSpec \| ((personality: Personality) => MotionSpec))、EnterInput(TransitionKind \| SpecInput)、MotionConfig、MotionProviderProps、MotionControls、PresenceState、PresenceOptions、CascadeOptions、MotionProps、PresenceProps。