コンテンツにスキップ

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-shadowshadowForElevation
interface PoseFrame extends Pose {
offset?: number;
easing?: Easing;
}

タイムライン上に置いたポーズです。offset は 0〜1 の位置で、省略すると WAAPI と同じ規則で配置されます(両端は 0 と 1、間は均等)。easingこのフレームから始まる区間 に適用されます。

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 を立体的に描く
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;
}

arcPointtiltToward が使う 2 次元の点です。

const REST: Readonly<Required<Pose>>;

すべてのポーズの基準となる静止状態です(x / y / z / 回転 / skew / blur / elevation は 0、scale / scaleX / scaleY / opacity は 1)。凍結済みのオブジェクトです。

function lerpPose(a: Pose, b: Pose, t: number): Pose;

2 つのポーズを線形補間します。どちらかにあるキーをすべて対象にし、ない側は REST の値とみなします。

function isRestPose(pose: Pose): boolean;

ポーズが無変形の要素と見た目上同一(全キーが REST との差 1e-6 未満)なら true を返します。

function scaleDeviation<P extends Pose>(pose: P, factor: number, rest?: Pose, keys?: readonly PoseKey[]): P;

poserest(既定 REST)からの偏差を factor 倍します。factor > 1 で遠ざけ、factor < 1 で近づけ、負数で反転します(反転したポーズがそのまま Anticipation の溜めになります)。keys の既定は opacity 以外の全キーです。結果は opacity を 0〜1、blur と elevation を 0 以上にクランプします。

function composePoses(layers: readonly Pose[]): Pose;

独立したレイヤー(ホバー + 押下 + チルトなど)を 1 つのポーズに合成します。平行移動・回転・skew・blur・elevation は 加算scale / scaleX / scaleY / opacity乗算 です。結果はクランプされます。

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)"
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() です。

function compile(spec: MotionSpec): CompiledMotion;
interface CompiledMotion {
keyframes: Keyframe[];
options: KeyframeAnimationOptions;
}

MotionSpecelement.animate() の引数に変換する純関数です。CSS のイージング文字列はそのまま渡し、JS のイージングを含む場合は密な線形キーフレームに焼き込みます(実行時の振る舞い)。optionsdelay 0、iterations 1、direction "normal"fill "both" を既定とします。フレームが 0 枚、または duration が負の場合は RangeError です。

function frameOffsets(frames: readonly PoseFrame[]): number[];

省略された offset を WAAPI と同じ規則で解決します(両端を 0 / 1 に固定し、間を均等に配置)。フレームが 1 枚なら [offset ?? 1] です。0〜1 の範囲外や降順があると RangeError です。

function sampleSpec(spec: MotionSpec, t: number): Pose;

1 イテレーション中の時間の進み具合 t(0〜1 にクランプ)でのポーズを、ブラウザのキーフレーム補間と同じ方法(全体のイージング → 区間のイージング → 線形補間)で求めます。モーションを途中で中断して滑らかに繋ぐために使われます。

function withoutGlobalEasing(spec: MotionSpec): MotionSpec;

spec 全体の easing を各フレームの区間イージングへ移し替えます。キーポーズは残し、offset と区間イージングだけが変わります。フレームを挿入・差し替える前(anticipatefollowThrough)に必要な変換です。全体のイージングが単調でない(行き過ぎる)場合は、60fps 相当で最低 12 枚のフレームに再サンプリングします。

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 行き過ぎて戻る
function cubicBezier(x1: number, y1: number, x2: number, y2: number): EasingFn;

CSS の cubic-bezier() と同じ曲線を JS 関数として返します。x1 / x2 が 0〜1 の外なら RangeError です。

function resolveEasing(easing: Easing | undefined): EasingFn;

任意の Easing を JS 関数に解決します。undefined"linear" は恒等関数、"ease" / "ease-in" / "ease-out" / "ease-in-out"cubic-bezier() は対応する曲線、関数はそのまま返します。それ以外(steps()、CSS の linear() など)は TypeError です。文字列ごとにキャッシュされます。

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 秒で全距離)

stiffnessdamping のどちらかを指定すると物理パラメータとして扱い、duration / bounce は無視されます。質量・剛性・減衰が正の有限値にならない場合(duration: 0 など)は RangeError です。spring() の既定値での .duration は 542ms です。

function isSpring(easing: unknown): easing is Spring;

数値の duration プロパティを持つ関数なら true を返します。

const durations: {
readonly instant: 100;
readonly fast: 150;
readonly base: 200;
readonly slow: 300;
readonly deliberate: 500;
};
type DurationToken = keyof typeof durations;

deliberate はシステム起点の演出(オンボーディング、空状態)専用です。

const USER_INITIATED_MAX_MS = 300;
const MAX_STAGGER_MS = 50;

ユーザー起点のモーションの尺の上限の目安と、スタッガーの 1 項目あたりの遅延の上限の目安です。MAX_STAGGER_MSStaggerOptions.cap の既定値として使われます(cap を指定すれば超えられます)。

function duration(value: DurationToken | number, tempo?: number): number; // tempo 既定 1

トークンまたは ms に tempo を掛けて Math.round します。値が負、または tempo が 0 以下なら RangeError です。

function travelDuration(distancePx: number, options?: TravelOptions): number;
TravelOptions 既定値 説明
min number 150 距離 0 のときの尺(ms)
max number 300USER_INITIATED_MAX_MS 最大の尺(ms)
reference number 800 max に達する距離(px)

距離の平方根に比例して min から max へ補間します。長い移動が間延びせず、短い移動がせわしなくなりません。100px で 203ms、400px で 256ms です。

function staggerDistances(count: number, from?: "first" | "last" | "center" | number): number[];

各項目がカスケードの起点から何ステップ離れているかを返します(0 = 最初に動く)。"center"(count - 1) / 2 を起点にするので、偶数個では 0.5 刻みになります。count が 0 以上の整数でなければ RangeError です。

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 で表現されます。

type Axis = "x" | "y";
function deform(amount: number, axis?: Axis): Pose; // axis 既定 "y"
function squash(amount?: number, axis?: Axis): Pose; // amount 既定 0.05
function stretch(amount?: number, axis?: Axis): Pose; // amount 既定 0.05
function squashStretch(options?: ImpactOptions): MotionSpec;
  • deform: 体積を保つ(scaleX × scaleY = 1)変形ポーズ。amount > 0axis 方向に伸び、< 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

function anticipate(spec: MotionSpec, options?: AnticipationOptions): MotionSpec;
function windUp(from: Pose, to: Pose, amount?: number): Pose; // amount 既定 0.15
  • anticipate: 最初の区間の前に逆方向の溜めフレームを挿入します。溜めは windUp(frames[0], frames[1], amount) で、offset share に置かれ、元の動作は残りの [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

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

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 枚のフレームにします。重力や揺らぎなど、キーポーズでは表せない動きに使います。durationfps が正でなければ RangeError です。
TimelineOptions 既定値 説明
duration number 300durations.slow 尺(ms)
delay number 遅延(ms)
easing Easing タイムライン全体のイージング。省略すると各キーで slow in / slow out
iterations number 繰り返し回数
direction PlaybackDirection 再生方向
origin string transform-origin
perspective number perspective() の距離(px)

StraightAheadOptionsTimelineOptions から easing を除き、duration: number(必須)と fps?: number(既定 60)を持ちます。

詳細: Straight Ahead & Pose to Pose

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

function arc(from: Pose, to: Pose, options?: ArcOptions): MotionSpec;
function arcPoint(a: Point, b: Point, bend: number, t: number): Point;
  • arc: fromtox / 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

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 500durations.deliberate 尺(ms)
delay number 遅延(ms)
iterations number 繰り返し回数

accompany は主動作の中に収まる副次動作を作ります。amplitudeattenuation、尺は primary.duration × (1 - lag)、遅延は primary.delay + primary.duration × lag です。

AccompanyOptions 既定値 説明
attenuation number 0.6 プリセットに対する強さ
lag number 0.15 主動作のこの割合だけ遅れて始める

詳細: Secondary Action

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)。offseteasing はそのまま残ります。factor が有限でなければ RangeError です。

ExaggerateOptions 既定値 説明
rest Pose REST 偏差の基準となる中立ポーズ
keys readonly PoseKey[] opacity 以外の全キー 拡大するキー

詳細: Exaggeration

function lift(level?: number): Pose; // level 既定 4
function tiltToward(point: Point, maxDeg?: number): Pose; // maxDeg 既定 8
function solid(spec: MotionSpec, perspective?: number): MotionSpec; // perspective 既定 800
  • lift: { y: -level × 0.5, elevation: level }。視点側へ浮き、影が大きく・柔らかく・薄くなります。
  • tiltToward: 面を point へ向ける回転。point は要素に正規化した座標(左上 (-0.5, -0.5)、右下 (0.5, 0.5)、範囲外はクランプ)で、rotateX = -y × 2 × maxDegrotateY = x × 2 × maxDeg です。
  • solid: spec に perspective を設定します。0 以下なら RangeError です。

詳細: Solid Drawing

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 を重ね、各値を上表の範囲で検証し(数値の範囲外は RangeErrorguardrails が boolean でなければ TypeError)、凍結したオブジェクトを返します。nameguardrails は省略すると 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 が掛かる

JumpOptionsRecipeOptionsheight?: number(既定 16、exaggeration が掛かる)を加えたものです。

関数 内容 既定の尺
enter 隠れたポーズ → 静止、easings.outpopfollowThrough(bounce は max(personality.bounce, 0.25) base 200ms × tempopopslow 300ms × tempo
exit 静止 → 隠れたポーズ、easings.inpopanticipation > 0 なら anticipateshare 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 を、guardrailstrue なら 0.02〜0.05、false なら 0.02〜0.2 にクランプした押下の縮小量
settleSpring spring({ duration: 350 × tempo, bounce })。押下・ホバー・チルトの解除用

隠れたポーズの一覧と各レシピの詳細は レシピ を参照してください。

UI ガイドラインの外へ意図して出るための、注意を引く動きと祝福の動きです。尺は基準 × tempoduration 指定で上書き)、振れ幅は 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%" 物理的な所要時間 × temponatural で 617ms)

expressive は名前からレシピを引く辞書です。使い分けと Personality ごとの尺は 派手な動き を参照してください。

play を中心とする DOM ランタイムには、次の約束があります。

  1. 1 要素につき 1 モーション。 play は、その要素で再生中のモーションを cancel() してから新しいモーションを始めます。キャンセルされた Animationfinished は reject されます。途切れなく繋ぐには、currentPose(el) を起点にするか、それを内部で行う animateTo を使います。
  2. 所有するプロパティ。 アニメーション対象の要素では、ライブラリが transform / transform-origin / opacity / filter / box-shadow を所有します。自前の CSS transform が必要なら、ラッパー要素に分けてください。
  3. 終了時の後始末(persist: true、既定)。 終了時に、終端のポーズ(directioniterations を考慮)を求めます。
    • 終端が静止ポーズ(isRestPose)なら、上記 5 つのプロパティを インラインスタイルから取り除き、アニメーションを解放します。要素はスタイルシートの状態に戻ります。
    • そうでなければ commitStyles() で終端をインラインスタイルに書き込み、アニメーションを解放します。fill で要素を固定し続けないためです。要素が描画されていない(display: none や DOM から外れている)ため commitStyles() が失敗した場合は、アニメーションの fill を残して元の状態へ戻らないようにします。
  4. persist: false 終了後にアニメーションを解放するだけで、インラインスタイルは書き込みません。要素は再生前の見た目に戻るので、「行って戻る」演出に使います。
  5. JS イージングの焼き込み。 CSS のイージング文字列はそのまま WAAPI に渡します。spec の easing かいずれかのフレームの easing が JS 関数(スプリング、cubicBezier など)なら、spec 全体を 60fps 相当でサンプリングし、線形のキーフレームに焼き込みます。区間数は round(duration / 1000 × 60) を 12〜120 にクランプした値で、キーフレームはその +1 枚です(300ms なら 19 枚)。CSS の linear() 関数には依存しません。
  6. reduced motion は再生前に適用。 reducedMotion"fade" / "skip" に解決されると、spec は reduceSpec で置き換えられてからコンパイルされます。
  7. 必要なブラウザ機能。 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 で元に戻る
function playAll(items: readonly PlayAllItem[], options?: PlayOptions): Promise<void>;
interface PlayAllItem {
element: Element;
spec: MotionSpec;
}

複数のモーションを同じオプションで同時に再生し、すべてが終わると解決します。途中でキャンセルされたモーションがあっても reject しません。

function stop(element: Element): void;

要素で再生中のモーションを、その場の見た目で止めます(commitStyles() してから解放)。止めた時点のポーズは currentPose で読めます。再生中のモーションがなければ何もしません。

function currentPose(element: Element): Pose;

要素がいま見せているポーズを返します。再生中なら、経過時間・遅延・イテレーション・再生方向から求めたその瞬間のポーズです。再生中でなければ、ライブラリが最後に止めた(または静止状態以外で終えた)ポーズです。どちらもなければ空のポーズ {}(= REST)です。

function reduceSpec(spec: MotionSpec, mode: "fade" | "skip"): MotionSpec;

空間的な動きを取り除いた spec を返します。フレームは [終端の形状 + 始端の opacity, 終端] の 2 枚で、尺は "fade" なら Math.min(spec.duration, 150)"skip" なら 0 です。"fade"delay を保ち、"skip" は 0 にします。originperspective は引き継ぎ、easing / iterations / direction は引き継ぎません。

function prefersReducedMotion(): boolean;

matchMedia("(prefers-reduced-motion: reduce)") が一致すれば true です。matchMedia がない環境では false を返します。

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" } });
function setLayer(
element: Element,
name: string,
pose: Pose | null,
options?: Omit<AnimateToOptions, "perspective">,
): Animation;

要素の名前付きレイヤーを設定(null なら解除)し、全レイヤーを composePoses で合成したポーズへ animateTo します。独立した振る舞いがそれぞれ自分のレイヤーを持つので、互いを上書きしません。組み込みの振る舞いは "press" / "hover" / "tilt" / "stage" を使います。perspectivesetPerspective で要素ごとに設定した値が使われます。

import { setLayer } from "twelve-principles";
setLayer(card, "drag", { rotate: 4, elevation: 8 }); // ホバーや押下と合成される
setLayer(card, "drag", null); // drag レイヤーだけを外す
function setPerspective(element: Element, perspective: number | undefined): void;

以後の setLayer の遷移すべてに perspective(px) を付けるよう宣言します(3D チルトや反転)。undefined で解除します。tiltable が内部で使います。

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")を受け取ります。

押下で素早く縮み(duration("instant", tempo) = 100ms、easings.out)、離すと settleSpring(personality) で戻ります。"press" レイヤーに { scale: 1 - depth } を設定します。

  • 押し込む: pointerdown(主ボタンのみ)、keydown の Enter / Space(キーリピートは無視)
  • 戻す: pointerup / pointercancel / pointerleavekeyup の Enter / Space、blur
PressOptions 既定値 説明
depth number pressDepth(personality)(0.02〜0.05、guardrails: false なら 0.02〜0.2) 押下中の縮小量。明示した値はクランプされない

マウス・ペンのホバーで視点側へ浮きます(タッチは無視)。"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 }

ポインタに追従して面を傾けます(タッチは無視)。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 を設定し、positionstatic なら relative にします。

release() は全要素の "stage" レイヤーを duration("base", tempo)(200ms)の easings.inOut で外し、すべてのモーションが終わると z-indexposition を元の値に戻して解決します。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 ガイド にあります。

エクスポート シグネチャ
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>

型: SpecInputMotionSpec \| ((personality: Personality) => MotionSpec))、EnterInputTransitionKind \| SpecInput)、MotionConfigMotionProviderPropsMotionControlsPresenceStatePresenceOptionsCascadeOptionsMotionPropsPresenceProps