mcpField
A
animate-ui

animate-ui

github.com/prototyperaiデザイン・UI781 回インストール23 週間前に更新
リポジトリ

animate-ui について

Prototyper UI コンポーネントの実装やレビュー向けに、アニメーション規約をまとめたスキルです。再利用可能な Tailwind クラス、時間・イージングの基準に加え、アクセシビリティと性能の制約を示します。

主な機能

  1. 01主要コンポーネントのアニメーションパターン
  2. 02イージング曲線と時間区分の定義
  3. 03オーバーレイの原点と拡大率の指定
  4. 04アニメーション対象プロパティと遷移の制約
  5. 05動きを抑えるアクセシビリティ規則

活用シーン

  1. 01オーバーレイの表示・終了アニメーションを追加する
  2. 02コントロールの押下フィードバックを統一する
  3. 03アニメーション性能と割り込み可能性を確認する
  4. 04動きを抑える代替設定を加える

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/prototyperai/prototyper-ui --skill animate-ui

インストール後、Claude Code で/animate-uiを入力して読み込まれたか確認します。

詳細

Prototyper UI — アニメーション規約

Prototyper UI コンポーネントのすべてのアニメーションに関する決定事項です。まずパターン、次に参照表、続いてルール、最後にコンポーネントごとの詳細を説明します。


1. コピー&ペースト用パターン

この5つのパターンで、アニメーション作業の約95%をカバーできます。クラス文字列をそのままコピーしてください。

パターン A:配置型オーバーレイ

使用箇所:Popover、Menu、Tooltip、Select、Combobox、Context Menu、HoverCard、Menubar。

className={cn(
  // Enter
  "data-open:animate-in data-open:duration-150",
  "data-open:[animation-timing-function:var(--ease-out-fluid)]",
  "data-open:fade-in-0 data-open:zoom-in-95",
  // Exit
  "data-closed:animate-out data-closed:duration-100",
  "data-closed:[animation-timing-function:var(--ease-in-quart)]",
  "data-closed:fade-out-0 data-closed:zoom-out-95",
  // Direction-aware slide (2 = 0.5rem)
  "data-[side=bottom]:slide-in-from-top-2",
  "data-[side=top]:slide-in-from-bottom-2",
  "data-[side=left]:slide-in-from-right-2",
  "data-[side=right]:slide-in-from-left-2",
  // Origin from trigger position
  "origin-(--transform-origin)",
  // Performance
  "data-entering:will-change-[opacity,transform]",
  "data-exiting:will-change-[opacity,transform]",
  // Accessibility
  "motion-reduce:animate-none motion-reduce:transition-none",
)}
パターン B:ダイアログ(中央)
// Backdrop
className={cn(
  "data-open:animate-in data-closed:animate-out",
  "data-closed:fade-out-0 data-open:fade-in-0",
  "duration-200",
  "bg-black/10 supports-backdrop-filter:backdrop-blur-xs",
  "motion-reduce:animate-none motion-reduce:transition-none",
)}

// Content — note the slight overshoot scale
className={cn(
  "data-open:animate-in data-closed:animate-out",
  "data-closed:fade-out-0 data-open:fade-in-0",
  "data-closed:zoom-out-[0.98] data-open:zoom-in-[1.02]",
  "duration-200 ease-out-fluid",
  "data-entering:will-change-[opacity,transform]",
  "data-exiting:will-change-[opacity,transform]",
  "motion-reduce:animate-none motion-reduce:transition-none",
)}

ダイアログでは zoom-in-[1.02](わずかなオーバーシュート)と zoom-out-[0.98] を使います。オーバーレイの zoom-in-95/zoom-out-95 より控えめです。

パターン C:シート(方向に応じて変化)
const sheetVariants = cva(
  // Base: transition + split enter/exit duration
  "transition ease-out-fluid " +
    "data-open:animate-in data-open:duration-300 " +
    "data-closed:animate-out data-closed:duration-200 " +
    "data-entering:will-change-[opacity,transform] " +
    "data-exiting:will-change-[opacity,transform] " +
    "motion-reduce:animate-none motion-reduce:transition-none",
  {
    variants: {
      side: {
        top: "data-open:slide-in-from-top data-closed:slide-out-to-top",
        bottom:
          "data-open:slide-in-from-bottom data-closed:slide-out-to-bottom",
        left: "data-open:slide-in-from-left data-closed:slide-out-to-left",
        right: "data-open:slide-in-from-right data-closed:slide-out-to-right",
      },
    },
  },
);

シートは端から端まで100%スライドします。配置型オーバーレイの slide-*-2 とは異なります。

パターン D:押下時のフィードバック
"motion-safe:active:scale-[0.97]"; // Standard interactive elements
"motion-safe:active:scale-[0.98]"; // Menu items, tab triggers
"motion-safe:active:scale-[0.95]"; // Small controls
"motion-safe:active:scale-100"; // Explicitly disabled (ghost/link buttons)

必ず motion-safe: プレフィックスを付けてください。active:scale-* をそのまま使ってはいけません。Checkbox では motion-safe:group-active:scale-[0.95](グループバリアント)を使います。

スケールコンポーネント
scale-[0.97]Button(デフォルト)、Switch(トラック)、Toggle、Toolbar(Button、Link)、Select(トリガー)
scale-[0.98]Menu 項目(Item、SubTrigger、CheckboxItem、RadioItem)、Tabs(トリガー)
scale-[0.95]Checkbox(コントロール)、RadioGroup(項目)、NumberField(Increment、Decrement)
scale-100Button の ghost バリアント、Button の link バリアント
パターン E:状態遷移
"transition-[color,background-color,border-color,box-shadow,opacity] duration-150 ease-smooth";
"motion-reduce:transition-none";

使用箇所:Button、Checkbox、Combobox(入力欄/チップ/チップ削除)、Field(FieldGroup)、Input、InputGroup、Menu 項目、NumberField、RadioGroup、Select(トリガー)、Tabs(トリガー)、TextField、Textarea、Toggle、Toolbar(ボタン/リンク/入力欄)、NavigationMenu(トリガー/リンク)、Accordion(トリガー)。


2. イージングトークン

apps/docs/registry/prototyper-tokens.css で定義されています。

トークン値用途
ease-smoothcubic-bezier(0.4, 0, 0.2, 1)状態遷移、画面上の変化
ease-out-fluidcubic-bezier(0.32, 0.72, 0, 1)表示される要素:オーバーレイ、シート、スイッチ/スライダーのつまみ
ease-in-quartcubic-bezier(0.895, 0.03, 0.685, 0.22)非表示になる要素:オーバーレイの閉じる動作
ease-out-quadcubic-bezier(0.25, 0.46, 0.45, 0.94)利用可能ですが、現在は未使用
ease-out-quartcubic-bezier(0.165, 0.84, 0.44, 1)利用可能ですが、現在は未使用
ease-in-quadcubic-bezier(0.55, 0.085, 0.68, 0.53)利用可能ですが、現在は未使用
ease-in-out-quadcubic-bezier(0.455, 0.03, 0.515, 0.955)利用可能ですが、現在は未使用
判断フロー
Is the element entering or exiting?
├── Entering the screen → ease-out-fluid
├── Exiting the screen → ease-in-quart
└── No (staying on screen)
    ├── Moving or morphing? → ease-smooth
    ├── Color/background change? → ease-smooth
    ├── Constant motion (spinner, progress)? → linear
    └── Default → ease-smooth
Tailwind での使用
  • 状態遷移:ease-smooth(直接ユーティリティ)
  • オーバーレイの表示:data-open:[animation-timing-function:var(--ease-out-fluid)] または ease-out-fluid
  • オーバーレイの終了:data-closed:[animation-timing-function:var(--ease-in-quart)]
  • 中央のダイアログ:ease-out-fluid(表示と終了の両方)
  • ナビゲーションメニュー:ease-[cubic-bezier(0.22,1,0.36,1)](ナビゲーション専用で、トークンではありません)
ease-out を使う理由

UI では基本的に ease-out を使用します。動き始めは速く、最後に減速するため、すぐに反応している印象になります。UI アニメーションに ease-in は絶対に使わないでください。最後に加速するため、動きが遅く反応しないように感じられます。300ms ease-out は 300ms ease-in よりも体感で速く感じられます。組み込みの CSS イージングカーブでは洗練された UI を実現できないため、必ずプロジェクト独自のトークンを使用してください。

3. 期間の段階

段階期間用途
即時100msアラートダイアログ、コンテキストメニュー、ホバーカード、メニューバー、ポップオーバー/ツールチップの終了
高速150ms状態遷移(すべてのインタラクティブ要素)、オーバーレイの開始
標準200msダイアログ(中央)、シートの終了、チェックボックスのコントロール/SVG、アコーディオン、ボタンの疑似要素
中程度250msスイッチのトラック、スライダーのつまみ
スムーズ300msシートの開始、スイッチのつまみ、メーター/プログレスバー
長め350msナビゲーションメニューのコンテンツ(大きなスライドパネル)
ルール
  • UI アニメーションは 300ms 未満にします(大きなパネルの場合、ナビゲーションメニューは 350ms の例外)
  • 終了アニメーションは開始アニメーションより 20~33% 速くします(150→100、300→200)
  • 大きな要素ほどアニメーションを遅くします(ダイアログ 200ms > ツールチップ 150ms > アラート 100ms)
  • 移動距離に合わせて期間を設定します。移動距離が長いほど期間を長くします
  • 長押しで確定する操作には、約 2s の linear を使用します。指を離したときは速くします(約 200ms の ease-out)
  • 頻繁に目にするアニメーションは煩わしくなります。短縮するのではなく、完全に削除します

4. スケール値

コンテキスト値クラス
オーバーレイの開始/終了0.95zoom-in-95 / zoom-out-95
ダイアログの開始1.02(オーバーシュート)zoom-in-[1.02]
ダイアログの終了0.98zoom-out-[0.98]
ナビゲーションメニューのポップアップ0.90scale-90(開始/終了スタイル)
ボタン/トグルの押下0.97scale-[0.97]
メニュー項目の押下0.98scale-[0.98]
小さなコントロールの押下0.95scale-[0.95]
スライダーのつまみのドラッグ0.85[&::after]:scale-[0.85]
押下なしの上書き1.0scale-100

scale(0) からのアニメーションは使用しないでください。唐突で不自然に感じられます。開始時の最小スケールは、スライダーのドラッグでは 0.85、ナビゲーションメニューでは 0.90 です。開始/終了アニメーションでは、必ずスケールと不透明度を組み合わせてください。


5. オーバーレイの原点指定

位置指定されるすべてのオーバーレイで、次を使用する必要があります。

origin-(--transform-origin)

これは transform-origin: var(--transform-origin) に対応します。Base UI が、ポップアップのトリガーに対する位置に基づいて自動的に設定します。これを指定しないと、オーバーレイはトリガーではなく中央を起点に拡大縮小され、トリガーとのつながりが感じられなくなります。

使用箇所: ポップオーバー、メニュー、ツールチップ、セレクト、コンボボックス、コンテキストメニュー、ホバーカード/プレビューカード、ナビゲーションメニューのポップアップ。


6. パフォーマンス上の制約

アニメーション対象は次のプロパティだけにします
  • transform(scale、translate、rotate)— 合成のみ
  • opacity — 合成のみ
  • clip-path — ハードウェアアクセラレーション対応、レイアウトシフトなし
次の操作は禁止です
  • transition-all — レイアウトプロパティもアニメーションし、カクつきの原因になります
  • width、height、padding、margin を直接アニメーションすること(例外:アコーディオンではキーフレームによる height アニメーションを使用できます)
  • 実行時に CSS カスタム変数を使ってアニメーションすること — CSS 変数を設定するとスタイルが再計算され、その影響がすべての子要素に波及します。代わりに transform を直接適用してください
  • will-change を恒常的に使用すること — アニメーション中のみ適用します:data-entering:will-change-[opacity,transform] data-exiting:will-change-[opacity,transform]
transition プロパティを明示的に列挙します

transition だけを使用してはいけません。対象とするプロパティを正確に列挙してください。

transition-[color,background-color,border-color,box-shadow,opacity]   → state transitions
transition-[color,background-color,border-color,box-shadow,transform] → checkbox (includes scale)
transition-[background-color,transform]                                → slider thumb
transition-[margin-inline-start,background-color]                      → switch thumb
transition-[width,background-color]                                    → meter/progress indicator
割り込み可能性

インタラクティブなアニメーションでは、CSS のキーフレームよりトランジションが優れています。トランジションはシーケンスの途中で目標を変更できるため、アニメーションの実行中に終点を変更すると、滑らかに方向を変えます。キーフレームは終点が固定され、きれいに割り込むことができません。

Prototyper UI のすべての状態遷移では、CSS の transition-*(完全に割り込み可能)を使用します。オーバーレイの表示/非表示ではキーフレーム(animate-in/animate-out)を使用します。アニメーションが短く(100-200ms)、継続的なユーザー操作ではなく、開閉状態によってトリガーされるため、問題ありません。


7. 判断基準

アニメーションを追加する前に、次の3つの質問に答えてください。

  1. 明確な目的はありますか? 状態の変化を説明する、レスポンスを示す、空間的なロジックを作る、または楽しさを加える必要があります。
  2. ユーザーはどのくらいの頻度で目にしますか? まれにしか表示されないものは、適した候補です。頻繁に表示されるものや、キーボード操作でトリガーされるものでは、アニメーションを完全に省略してください。
  3. 体感パフォーマンスは向上しますか? 遅延を発生させたり、操作を妨げたりする場合は、再検討してください。

厳守するルール:

  • キーボード操作でトリガーされるアクションは、決してアニメーションさせないでください。遅く、もたつき、操作とのつながりが感じられなくなります。
  • セッションあたり数百回使われるアクション(例:リスト項目のホバー)は、決してアニメーションさせないでください。
  • ゴーストボタンとリンクボタンには motion-safe:active:scale-100 を適用します(押下時の拡大縮小は明示的に行いません)。
  • タブのコンテンツパネルには表示/非表示のアニメーションを適用しません(即時に切り替えます)。

8. アクセシビリティチェックリスト

アニメーションするすべての要素に、該当する項目をすべて適用する必要があります。

  • transition-* クラスを持つ要素には motion-reduce:transition-none を適用する
  • animate-in/animate-out/animate-* クラスを持つ要素には motion-reduce:animate-none を適用する
  • 押下時のフィードバックとして使うすべての active:scale-* には motion-safe: プレフィックスを付ける(単独では使わない)
  • タッチ操作では発生させないホバー効果には hover-only:hover: を付ける(ホバー状態の固着を防ぐ)
  • 疑似要素の拡大縮小効果(Slider)には motion-reduce:[&::after]:scale-100 を適用する
  • 疑似要素のトランジション(Tabs)には motion-reduce:after:transition-none を適用する

カスタムバリアント(prototyper-tokens.css で定義):

@custom-variant motion-reduce (@media (prefers-reduced-motion: reduce));
@custom-variant motion-safe (@media (prefers-reduced-motion: no-preference));
@custom-variant hover-only (@media (hover: hover));

アニメーションは吐き気や注意散漫の原因になることがあります。必ず、動きを抑えた代替手段を用意してください。バウンスよりフェードを、スライドより即時切り替えを優先してください。


9. コンポーネント別のオーバーレイクラス

各オーバーレイコンポーネントの正確なクラス文字列です。ほとんどは、タイミングやイージングにバリエーションを加えたパターンAに従います。

Popover(標準 — 表示は150ms ease-out-fluid、非表示は100ms ease-in-quart)
data-open:animate-in data-open:duration-150 data-open:[animation-timing-function:var(--ease-out-fluid)]
data-open:fade-in-0 data-open:zoom-in-95
data-closed:animate-out data-closed:duration-100 data-closed:[animation-timing-function:var(--ease-in-quart)]
data-closed:fade-out-0 data-closed:zoom-out-95
data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2
data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2
origin-(--transform-origin)
data-entering:will-change-[opacity,transform] data-exiting:will-change-[opacity,transform]
motion-reduce:animate-none motion-reduce:transition-none
Tooltip(Popover と同じタイミング)
data-open:animate-in data-open:duration-150 data-open:[animation-timing-function:var(--ease-out-fluid)]
data-open:fade-in-0 data-open:zoom-in-95
data-closed:animate-out data-closed:duration-100 data-closed:[animation-timing-function:var(--ease-in-quart)]
data-closed:fade-out-0 data-closed:zoom-out-95
data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2
data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2
origin-(--transform-origin)
data-entering:will-change-[opacity,transform] data-exiting:will-change-[opacity,transform]
motion-reduce:animate-none motion-reduce:transition-none
Menu/DropdownMenu(表示は150ms、非表示は150ms — 両方向とも同じ時間)
data-open:animate-in data-open:duration-150 data-open:[animation-timing-function:var(--ease-out-fluid)]
data-closed:animate-out data-closed:duration-150 data-closed:[animation-timing-function:var(--ease-in-quart)]
data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95
data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2
data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2
origin-(--transform-origin)
data-entering:will-change-[opacity,transform] data-exiting:will-change-[opacity,transform]
motion-reduce:animate-none
Select(duration-150 ease-smooth、表示/非表示でタイミングを分けない)
data-open:animate-in data-closed:animate-out
data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95
data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2
data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2
duration-150 ease-smooth origin-(--transform-origin)
data-entering:will-change-[opacity,transform] data-exiting:will-change-[opacity,transform]
motion-reduce:animate-none
Combobox(表示はduration-150、非表示の時間は明示的に指定しない)
data-open:animate-in data-open:duration-150 data-closed:animate-out
data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95
data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2
data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2
origin-(--transform-origin)
data-entering:will-change-[opacity,transform] data-exiting:will-change-[opacity,transform]
motion-reduce:animate-none
Context Menu、HoverCard/PreviewCard(duration-100 + インラインの sides)
data-open:animate-in data-closed:animate-out
data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95
duration-100
data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2
data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2
data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2
origin-(--transform-origin) motion-reduce:animate-none
Alert Dialog(duration-100、スライドなし、起点なし)
Overlay: data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 duration-100
Content: data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 duration-100
Menubar(duration-100、DropdownMenu を継承)
data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 duration-100
+ directional slides + data-[side=inline-start/end] slides
Drawer(オーバーレイのみ — コンテンツは vaul が処理)
data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0

10. コンポーネント固有のパターン

Checkbox の SVG 描画アニメーション
<path
  strokeDasharray={22}
  style={{
    strokeDashoffset: "var(--check-offset, 66)",
    transition: "stroke-dashoffset 200ms linear 50ms",
  }}
/>
// Toggle: group-data-checked:[--check-offset:44]
// Unchecked: offset 66 (stroke hidden). Checked: offset 44 (stroke draws on).
// 50ms delay lets background fill animation begin first. Linear for consistent draw speed.
Slider のドラッグ
cursor-grab data-dragging:cursor-grabbing data-disabled:cursor-default
data-dragging:[&::after]:scale-[0.85]  motion-reduce:[&::after]:scale-100
transition-[background-color,transform] duration-250 ease-smooth
[&::after]:transition-transform [&::after]:duration-250 [&::after]:ease-out-fluid
motion-reduce:transition-none [&::after]:motion-reduce:transition-none
NumberField のスクラブ領域
cursor-ew-resize touch-none
Accordion
data-open:animate-accordion-down data-closed:animate-accordion-up
h-(--accordion-panel-height) data-ending-style:h-0 data-starting-style:h-0

キーフレーム:0.2s ease-out(トークン内で --animate-accordion-down/up として定義)。

ナビゲーションメニュー(独自の複雑なパターン)
  • コンテンツ:duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)]
  • 大きなスライド距離:slide-in-from-right-52、slide-in-from-left-52
  • アクティベーション方向のスタイル:data-starting-style:data-activation-direction=left:translate-x-[-50%]、data-starting-style:data-activation-direction=right:translate-x-[50%]
  • コンテンツの不透明度:data-ending-style:opacity-0 data-starting-style:opacity-0
  • ポジショナー:transition-[top,left,right,bottom] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)]
  • ポジショナーの即時モード:data-instant:transition-none(順次ホバー用)
  • ポップアップ:origin-(--transform-origin)、data-starting-style:scale-90 data-starting-style:opacity-0
  • ポップアップのトランジション:transition-[opacity,transform,width,height,scale,translate] duration-[0.35s]
  • インジケーター:data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in
  • シェブロン:transition duration-300 group-data-open/navigation-menu-trigger:rotate-180
  • トリガーの状態:transition-[color,background-color,border-color,box-shadow,opacity] duration-150 ease-smooth
スイッチ
  • トラック:transition-[color,background-color,box-shadow,opacity] duration-250 ease-smooth + motion-safe:active:scale-[0.97] + motion-reduce:transition-none
  • つまみ:transition-[margin-inline-start,background-color] duration-300 ease-out-fluid + motion-reduce:transition-none
プログレス/メーター
  • インジケーター:transition-[width,background-color] duration-300 ease-smooth + motion-reduce:transition-none
  • 不確定状態:data-[indeterminate]:animate-pulse + motion-reduce:animate-none
長めの状態遷移(パターン E からの逸脱)
コンポーネントプロパティ期間イージング
チェックボックスのコントロールcolor,background-color,border-color,box-shadow,transform200msease-smooth
コンボボックスの項目/チップの削除colors200msease-smooth
スイッチのトラックcolor,background-color,box-shadow,opacity250msease-smooth
スイッチのつまみmargin-inline-start,background-color300msease-out-fluid
スライダーのつまみbackground-color,transform250msease-smooth
スライダーのつまみ ::aftertransform250msease-out-fluid
メーター/プログレスのインジケーターwidth,background-color300msease-smooth
疑似要素のトランジション
コンポーネント要素クラス
ボタン(デフォルト)::afterafter:transition-opacity after:duration-200
ボタン(アウトライン)::afterafter:transition-colors after:duration-200 after:ease-out
タブ(トリガー)::afterafter:transition-opacity + motion-reduce:after:transition-none
NavigationMenuシェブロンアイコンtransition duration-300 group-data-open/navigation-menu-trigger:rotate-180
読み込み中/不確定状態
  • プログレス:data-[indeterminate]:animate-pulse + motion-reduce:animate-none
  • Toast:Loader2Icon に animate-spin(その他すべての Toast のアニメーションは sonner が処理)
Drawer/Toast

アニメーションは、それぞれ vaul と sonner のライブラリで処理されます。カスタムのアニメーションクラスは追加せず、代わりにライブラリの props を設定してください。

アニメーションなしのコンポーネント

Avatar、Collapsible、Label、Separator — 設計上、アニメーション/トランジション/モーションのクラスは一切使用しません。

ScrollArea

最小限:ScrollBar に transition-colors(ブラウザーのデフォルトのタイミング)。


11. 高度なテクニック

Clip-path

ハードウェアアクセラレーションに対応し、レイアウトシフトは発生しません。clip-path: inset(top right bottom left):

値効果
inset(0)完全に表示
inset(100%)完全に非表示
inset(0 0 100% 0)下から非表示
inset(0 100% 0 0)右から非表示
inset(0 0 50% 0)下半分を非表示
inset(50% 0 0 0)上半分を非表示

角を丸くするには、clip-path: inset(0 75% 0 0 round 17px) を追加します。

用途:画像の表示演出、比較スライダー、テキストマスク効果、タブのトランジション、スクロールに連動した表示演出、長押しでの削除時の塗りつぶし、テーマ切り替え。推奨イージング:cubic-bezier(0.77, 0, 0.175, 1)。

ぼかしによるつなぎ

アニメーションの長さとイージングを調整しても違和感が残る場合は、トランジション中に filter: blur(2px) を追加します。状態間の視覚的な隙間を埋め、滑らかなトランジションとして認識されるようにします。縮小効果と相性が良い方法です。現在、Prototyper の UI コンポーネントでは使用していません。モーフィングアニメーションの最後の手段として検討してください。

割り込み可能な設計

ユーザーがアニメーションの途中でも、スムーズに状態を変更できるようにします。完了するまでユーザーの操作を受け付けないアニメーションは、応答性に欠けると感じられます。CSS トランジションは自然に割り込みをサポートします。最初のトランジションが終わる前に終了値を再設定でき、アニメーションは滑らかに方向を変えます。

ジェスチャーとドラッグの処理
  • モーメンタム速度: Math.abs(swipeAmount) / timeTaken として計算し、距離のしきい値に達していなくてもフリックで閉じるためのしきい値を 0.11 に設定します
  • 減衰/抵抗: 制約される方向にドラッグする場合(例:ドロワーが最上部にあるときの上方向へのドラッグ)、距離が伸びるほど抵抗を強めます。つまり、移動量を小さくします。iOS のネイティブ動作に近い挙動です。
  • スクロールとドラッグの競合: コンテンツが最上部までスクロールされていない間は、ドラッグジェスチャーをブロックします。最上部に到達した後、誤って閉じるのをスクロールのモーメンタムで防ぐため、100ms のタイムアウトを追加します。
  • マルチタッチ: 最初のタッチが離されるまで、それ以降のタッチはすべて無視します。ユーザーが指を切り替えたときに要素の位置が急に変わるのを防ぎます。
  • ポインターキャプチャ: スワイプ/ドラッグ中に element.setPointerCapture(event.pointerId) を使用すると、ジェスチャーを失わずにポインターを要素の境界外へ移動できます。
  • 摩擦: 「逆方向」にドラッグした際に急停止させるのではなく、抵抗をかけます。より自然な感触になります。
トーストアニメーション(sonner)

すべてのトーストアニメーションは sonner ライブラリで処理します。

  • 表示:translateY(100%) → translateY(0)、400ms の ease
  • スタック:translateY(calc(var(--lift-amount) * var(--toasts-before))) による絶対配置、拡大縮小率は 0.05 * index
  • スワイプで閉じる:モーメンタムのしきい値 0.11、ポインターキャプチャ、上方向への摩擦
  • ホバー時の隙間::after 疑似要素でスタックされたトースト間の隙間を埋めます
  • タブの表示状態:visibilitychange を介して document.hidden の間はタイマーを一時停止します

カスタムのトーストアニメーションは実装せず、sonner の props を設定してください。

ドロワーアニメーション(vaul)

すべてのドロワーアニメーションは vaul ライブラリで処理します。

  • 特徴的なカーブ:cubic-bezier(0.32, 0.72, 0, 1) = 独自の --ease-out-fluid(500ms、iOS の Sheet に一致)
  • 背景の拡大縮小:ドラッグの進行度に比例して、ページラッパーに transform と border-radius を適用します
  • スナップポイント:リリース時に最も近いスナップポイントを選択します。ビューポートに対する比率と固定ピクセル値に対応し、モーメンタムに基づくスキップも可能です
  • キーボード:Visual Viewport API(visualViewport の resize イベント)でドロワーの高さを調整します

カスタムのアニメーションクラスは追加せず、vaul の props を設定してください。

ツールチップと順次操作

Base UI の TooltipProvider は、ツールチップのグループ化を標準で処理します。

  • 最初のツールチップ:誤操作による表示を防ぐため、遅延が設定されます
  • 同じグループ内の2つ目以降のツールチップ:遅延とアニメーションを完全に省略します
  • 実装:transition-duration: 0ms を指定した data-instant 属性

コメント