mcpField
M
motion-and-haptics

motion-and-haptics

github.com/zakariafデザイン・UI63160 回インストール5 週間前に更新
リポジトリ

motion-and-haptics について

Flutter 開発者向けのモーションと触覚フィードバックの設計スキルです。操作ごとの演出、視覚効果を減らす設定への対応、触覚の割り当てを定め、アニメーションなしでも状態が正しく保たれるようにします。

主な機能

  1. 01操作ごとの演出宣言
  2. 02視覚効果を減らす設定への対応
  3. 03一元化された触覚イベント割り当て
  4. 04確定イベントごとに一度の触覚
  5. 05中断可能で上限のある演出
  6. 06反復アニメーションの停止条件

活用シーン

  1. 01押下や確定時のフィードバック設計
  2. 02拒否や成功に触覚を割り当て
  3. 03視覚効果を減らす設定への対応
  4. 04祝賀演出やループアニメーションの制限

インストール

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

$npx skills add https://github.com/zakariaf/flutter-skills --skill motion-and-haptics

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

同梱ファイルreferences/moment-catalog.md

詳細

モーションと触覚フィードバック

何が、いつ、正確に一度だけ発火し、どのように変化するか。フィードバックは、ユーザーが読むのではなく感じるレイヤーです。タップが確実に反応し、エラーが伝わり、成功が認められるアプリは完成された印象になります。同じアプリでも、デフォルトの Material フィードバックではプロトタイプのように感じられます。

スコープの境界。 モーションのトークン — 時間の役割、カーブのスロット、生の値を許可しないルール、そして reduced motion の場合に resolveMotion が Duration.zero に集約される仕組み — は design-system-structure の担当です。ペインターやティッカーの仕組みは custom-canvas-and-gestures の担当です。メッセージをどのサーフェスで表示するか — ローディング、空の状態、エラー、スナックバー、バナー、ダイアログ、元に戻す操作、スクリーンリーダーへの通知 — は ui-states-and-feedback の担当です。このスキルが扱うのは、これらの判断を支える感覚的なレイヤーです。どのような瞬間があるのか、それぞれが何を確定するのか、触覚フィードバックをどう割り当てて発火させるのか、そしてアニメーションがなくても何が残るのかを定めます。

譲れないルール

  1. 確定を伴うすべての操作は、操作が起きたのと同じフレームで応答します。 指が触れた時点で、すぐに押下状態を表示します。確定時には、その確定に対応するフィードバックを続けて表示します。理由:非同期の結果が返るまで何も表示しないコントロールは、タップを受け付けなかったコントロールと見分けがつかず、ユーザーがもう一度タップしてしまいます。

  2. モーションは状態を装飾するものであり、状態そのものではありません。 すべてのアニメーションを無効にしても、最終状態は同一で、正しく、到達可能でなければなりません。アニメーションの実行中にしか存在しない選択状態や、遷移中にしか判読できない値は、reduced motion を使うユーザーが毎回直面する不具合です。

  3. 瞬間に名前を付け、それぞれを意図的に設計します。 どのアプリにも、小規模なカタログがあります。押下の応答、入力の拒否、確定時の手応え、成功、進捗や連続達成数の増加、そして特徴的な画面遷移です。それぞれの瞬間について、トリガー、変化するもの、使用する時間の役割とカーブ、触覚フィードバックのスロット、そしてreduced motion 時のフォールバックの5つを定義します。指定されていない瞬間は「最小限」なのではなく、誰も選んでいないフレームワークのデフォルトです。references/moment-catalog.md を参照してください。

  4. すべてのアニメーションについて、reduced motion 時の動作を設計時に定義します。 「小さな動きだから、誰も気にしない」はフォールバックではありません。仕組み(resolveMotion → Duration.zero)は design-system-structure の担当ですが、クロスフェードで最終状態に移る、完了した状態で表示する、演出を完全に省くといった判断は、ここで瞬間ごとに行い、記録します。機能コードは、アニメーションのたびにプラットフォームフラグを読み直してはいけません。

  5. 触覚フィードバックは一元的な対応表に従い、呼び出し箇所に散らばらせません。 HapticTheme 拡張では、各イベントに対して HapticFeedback の動詞を最大1つ割り当てます。選択は selectionClick 以下、確定は lightImpact/mediumImpact 以下、拒否は lightImpact 以下、成功は mediumImpact 以下とし、heavyImpact は本当のマイルストーンに限ります。null のスロットは明示的な無音であり、指定漏れではありません。理由:呼び出し箇所ごとに場当たり的に追加される触覚フィードバックは、強度が際限なく上がり、アプリが振動し続ける原因になります。

  6. 確定したイベントごとに、確定フレームで触覚フィードバックを1回だけ発火させます。 アニメーションのコールバック内やフレームごとには発火させません。また、まとめられたジェスチャーの入力ごとにも発火させません。ドラッグで発火させるのは、開始時と確定時の最大2回です。理由:フレームごとの触覚フィードバックは単なる振動になり、まとめられた入力ごとの触覚フィードバックは、素早いジェスチャーをガタガタとした振動にしてしまいます。

  7. heavyImpact をエラー音として使ってはいけません。 失敗時に強度を上げるのは、UI で防げたはずのミスをユーザーに罰することになります。拒否は伝えますが、強調しません。

  8. アプリ内の1つの切り替え設定で、祝福演出も含むすべての触覚フィードバックを制御します。 プラットフォームには大まかなシステムの触覚設定がありますが、触覚フィードバックを発火するアプリには独自のスイッチも必要です。また、呼び出し箇所ごとに確認するフラグではなく、中央で一元的に制御しなければなりません。

  9. すべてのアニメーションは中断可能にします。 トランジション中やお祝いの演出中にタップされた場合は、ただちに終了状態にします。演出中は入力を受け付けない、という状態を決して作らず、成功を待たせないでください。理由:ユーザーがお祝いの演出を2回目に見るときには、演出を飛ばして先に進みたいものです。

  10. お祝いの演出には上限を設けます。 要素数と次の操作可能な状態までの時間に上限を設け、画面の端ではなく操作した箇所から始め、1回だけ再生し、ループさせません。失敗状態には、ユーザーを責めるような動きを使わず、状況を認識したことを伝えます。理由:無制限のお祝いの演出は、ユーザーが求めていないロード画面と同じです。

  11. 繰り返しアニメーションや環境アニメーションには、停止条件を必ず定義します。 対象のルートが現在表示されていないとき、アプリがバックグラウンドにあるとき、またはモーションを減らす設定が有効なときに停止します。停止条件のない repeat() は、どのテストでも検出できず、どのレビュアーも気づかないバッテリー消費の不具合です。

  12. フィードバックは複数の経路で重ねて伝えます。 モーションを減らす設定が有効でも、残された経路である触覚と音は使えるため、状態変化をモーションだけで伝えることはできません。(色だけでの伝達は accessibility-as-code が担当します。これは、それと同じルールをモーションだけでの伝達に適用するものです。)

  13. 音を使う場合は、3つ目の経路とし、最初の経路にはしません。 プラットフォームの消音時の動作を尊重し、短く保ち、何が起きたかを理解するために音を必要とせず、触覚と同様に中央の切り替え設定で制御します。

  14. トランジションは読み取り方向に従いますが、固定されたコンテンツ面の表示方向は反転しません。 水平方向のプッシュはナビゲーション方向に合わせて RTL で反転します。一方、独自のレイアウトが読み取り方向に依存しないキャンバス、ボード、チャート、メディア面はそのまま維持します。方向のジオメトリは i18n-rtl-l10n が担当します。

モーメントの宣言

実装する前に、各モーメントを記録します。表そのものが仕様です。列を埋められない実装は、そのモーメントの設計が完了していません。

モーメントトリガー変化するもの時間の役割触覚モーションを減らす設定時の代替
押下ポインターを押す押下時の変形最短selectionClick即座に状態を反映し、変形はなし
拒否無効な入力上限付きの揺れ+メッセージ短く、中断可能lightImpactメッセージのみ。揺れはなし
確定状態の確定変化そのもの中程度lightImpact終了状態を即座に表示
成功タスクの完了認識の表示+結果長く、スキップ可能mediumImpactすでに完了した状態で結果を表示
増加カウンターの進行強調のパルスを1回短いselectionClick またはなし新しい値を表示し、パルスはなし
トランジションルートの変更特徴的なトランジション中程度なしクロスフェードまたはなし

ここで示す時間は数値ではなく、役割です。数値は design-system-structure が管理するトークンです。

触覚フィードバックを正確に1回発生させる

// The event map lives in the theme; call sites name an event, never an intensity.
@immutable
class HapticTheme extends ThemeExtension<HapticTheme> {
  const HapticTheme({this.select, this.commit, this.reject, this.success});

  /// A null slot is a DECLARED silence — the design said no haptic here.
  final Future<void> Function()? select;
  final Future<void> Function()? commit;
  final Future<void> Function()? reject;
  final Future<void> Function()? success;
  // copyWith / lerp omitted — see design-system-structure for the ThemeExtension contract.
}

// One central gate; no call site remembers to check the setting.
void fire(WidgetRef ref, Future<void> Function()? slot) {
  if (!ref.read(hapticsEnabledProvider)) return;
  slot?.call();
}

// At the commit — once, on the frame the state actually changed (rules 6, 8).
void onCommit(BuildContext context, WidgetRef ref, EditIntent intent) {
  ref.read(itemsProvider.notifier).apply(intent);          // state first
  fire(ref, Theme.of(context).extension<HapticTheme>()!.commit);
}

HapticFeedback.* を AnimationController のリスナー、onEnd コールバック、またはアイテムごとのループから呼び出さないでください。これら3か所では、1つのイベントが連続して何度も発生してしまいます。

アンチパターン

  • 押下状態のないコントロール — 「操作が登録された?」と確認するためのダブルタップは、すべてここから始まります。
  • アニメーション中にしか存在しない状態 — アニメーションをオフにしても、アプリが正しく動作し、内容を読み取れる必要があります。
  • 視差効果を減らす設定時の動作が設計されていないアニメーション、または機能コードがアニメーションごとにプラットフォームのフラグを読み直すこと。
  • HapticFeedback.mediumImpact()を呼び出し箇所に散りばめること — イベントを一度だけスロットに割り当てます。場当たり的な触覚フィードバックは、強度が上がる一方です。
  • アニメーションリスナー、onEnd、またはまとめられたジェスチャーの項目ごとに発生する触覚フィードバック — 確定したイベントごとに1回、確定フレームで発生させます。
  • エラーにheavyImpactを使うこと — 失敗を知らせるのであって、罰してはいけません。
  • スキップできない祝福アニメーション、または演出中の入力ブロック — タップですぐに解決できるようにします。
  • ループする祝福アニメーション、画面端から出現するパーティクル、または無制限の要素数 — 上限を設けます。
  • 「悲しげな」失敗アニメーション — ユーザーにはすでにわかっています。
  • 停止条件のないrepeat() — 画面遷移後、バックグラウンド中、視差効果を減らす設定時には停止する必要があります。
  • 唯一の合図となるサウンド、またはプラットフォームのサイレント動作を無視するサウンド。
  • RTLでコンテンツ面を反転すること — ナビゲーションの方向は反転しますが、方向に依存しないキャンバスは反転しません。

完了の定義

  • 確定を伴うすべての操作に、同じフレーム内で応答します。
  • 対象となるすべての場面で宣言の各行が記入されています。内容は、トリガー、変更、時間の役割、触覚フィードバックのスロット、視差効果を減らす設定時の代替動作です。
  • アニメーションを無効にしても最終状態は同じで、そこに到達できます。モーションだけに依存するものはありません。
  • 触覚フィードバックはHapticThemeマップから呼び出され、イベントの強度上限以下で、確定したイベントごとに1回、確定フレームで発生し、中央の1つの切り替えで制御されます。
  • nullの触覚フィードバックのスロットは意図的な無音として設定され、その旨が記録されています。
  • すべてのアニメーションを中断できます。タップすると最終状態になり、入力がブロックされることはありません。
  • 祝福アニメーションには上限があり、操作した場所から始まり、1回だけ再生され、ループしません。
  • 繰り返しアニメーションには、画面遷移後、バックグラウンド中、視差効果を減らす設定時の明示的な停止条件があります。
  • サウンドを使う場合は、補助的で、短く、サイレントモードに対応し、中央で制御されます。
  • 画面遷移は読み取り方向を尊重し、方向に依存しないコンテンツ面は再反転しません。

関連スキル

  • モーションのトークン、カーブのスロット、このスキルで使用するresolveMotionヘルパーについては、design-system-structureを参照してください。Materialがデフォルトでマウントする3つのアニメーションも含まれます。
  • AnimationControllerからrepaint:としてキャンバスアニメーションを動かす方法や、RepaintBoundaryの配置については、custom-canvas-and-gesturesを参照してください。
  • 場面がフィードバックする対象となる状態については、ui-states-and-feedbackを参照してください。読み込み/空/エラーの描画、スナックバー/バナー/ダイアログの段階的な使い分け、取り消し、SemanticsService.announceについて説明しています。
  • このスキルの「モーションだけに依存しない」というルールの元になった「色だけに依存しない」というルールや、プラットフォームのアクセシビリティフラグの読み取り方については、accessibility-as-codeを参照してください。
  • 破棄、境界の予算管理、重く感じる処理のプロファイルモードでの計測については、flutter-performanceを参照してください。
  • RTLでの方向に応じたジオメトリについては、i18n-rtl-l10nを参照してください。
  • 宣言したすべての場面について、視差効果を減らす設定のオン/オフ両方で動画を使って検証するビルド終盤の工程については、design-review-workflowを参照してください。

References

コメント