プロンプトとコンテキスト
あるプロダクトにおいて、ネイティブナビゲーション、アクセシビリティ、ローエンドデバイスでのパフォーマンスを損なうことなく、一覧、詳細、フィルター適用済みビューの間で一貫性のあるトランジションを実現したいと考えています。同一ドキュメントの更新には document.startViewTransition() を、クロスドキュメントのナビゲーションには CSS 宣言を使用し、API が利用できない場合、DOM 更新が失敗した場合、またはユーザーが視覚効果の抑制を希望する場合の挙動を説明するプログレッシブエンハンスメント計画を設計してください。
これはフロントエンド、デザインシステム、パフォーマンスの職種に適した設問です。MDN、Chrome for Developers、および CSS View Transitions 仕様は、ViewTransition のライフサイクル、同一ドキュメントおよびクロスドキュメントのメカニズム、疑似要素アニメーションツリー、スキップの挙動を定義しています。この記事は公開された標準仕様に基づくものであり、特定企業の面接問題バンクを主張するものではありません。
面接官が評価するポイント
面接官は、ナビゲーションのセマンティクス、DOM 更新、アニメーションのライフサイクル、フォールバックの境界を適切に分離できているかを評価しています。優れた回答では、ready、updateCallbackDone、finished、skipTransition()、prefers-reduced-motion、一意の view-transition-name 値、同一生成元(same-origin)の制限について言及します。不十分な回答は、失敗時の計画を持たずにフェードインの CSS ルールを追加するだけにとどまります。
確認のための質問
- 対象は SPA の状態変更ですか、クロスドキュメントのナビゲーションですか、それとも両方ですか?
- どの要素が共有 ID を必要とし、詳細画面を開く際にリストカードには安定した ID がありますか?
- コアとなるナビゲーションとインデックス登録は、JavaScript なしでも機能する必要がありますか?
- プロダクトにはどのようなアニメーション時間、ローエンドデバイスの予算、視覚効果抑制の要件がありますか?
30秒での回答
「ネイティブナビゲーションと状態更新を信頼できる唯一の情報源(source of truth)として維持し、View Transitions をプログレッシブエンハンスメントとして使用します。SPA の場合、同期または非同期の DOM 更新を startViewTransition(update) でラップし、ready の後に疑似要素をアニメーション化します。マルチページナビゲーションの場合は、共有要素に安定した名前を付けた同一生成元の @view-transition { navigation: auto; } を使用します。API が存在しない場合、更新が失敗した場合、または視覚効果の抑制が要求されている場合は、アニメーションなしで更新を完了し、必要に応じて skipTransition() を呼び出します。一意の名前、タイムアウト、メトリクスにより、トランジションがプロダクトの動作をブロックするのを防ぎます。」
ステップごとの解決策
同一ドキュメントのトランジションには、document.startViewTransition(() => update()) を呼び出します。ブラウザは古い状態をキャプチャし、更新コールバックを実行して、新しい状態を作成します。返される ViewTransition は、ready、updateCallbackDone、finished の各 Promise を公開します。コールバックが例外をスローした場合でも、エラーハンドリングはアプリケーション側の責務であり、ビジネスロジックの送信がアニメーションの成功に依存してはなりません。::view-transition-old(root) や ::view-transition-new(root) などの疑似要素を使用してトランジションをカスタマイズします。
view-transition-name を使用して、古い要素と新しい要素をペアリングします。名前は1つのトランジションツリー内で一意である必要があります。リストには配列のインデックスではなく安定したビジネス ID が必要です。そうしないと、ソートやフィルタリングによって誤った要素同士がペアになってしまいます。重要な要素のみに名前を付けてください。スナップショットやコンポジターサーフェスが多すぎるとコストが増加します。
クロスドキュメントのトランジションは、同一生成元のナビゲーションと CSS の @view-transition 宣言に依存します。JavaScript はナビゲーションのために startViewTransition() を呼び出しません。これはマルチページアプリケーションで機能しますが、ブラウザのサポート、同一生成元ポリシー、ページの CSS に依存します。リンクはトランジションなしでも通常どおり遷移できなければならず、アニメーションの Promise がサーバーのレスポンスやデフォルトのリンク動作をブロックしてはなりません。
ライフサイクルの処理にはタイムアウトとスキップのパスが必要です。ready が予算内に解決(settle)しない場合は、理由を記録して skipTransition() を呼び出します。戻るナビゲーション、フォーム送信、ページの離脱では、ナビゲーションを優先する必要があります。finished はクリーンアップとメトリクスにのみ使用し、データが保存された証拠として使用してはなりません。非同期データの場合、トランジションを await すべきかどうかを決定する前に、状態確定(state-commit)の境界を定義してください。
アクセシビリティとは、アニメーション時間をゼロに設定するかスキップすることで @media (prefers-reduced-motion: reduce) を尊重することを意味します。フォーカス、見出し、読み上げ順序を維持してください。視覚的な位置がセマンティックな更新の代わりになることはありません。キーボードユーザー、スクリーンリーダー、低速なデバイスでも、まったく同じコンテンツの結果が得られる必要があります。
プログレッシブエンハンスメントは、機能検出と実際のナビゲーションフォールバックを組み合わせます。document.startViewTransition が存在しない場合は、更新関数を直接呼び出します。クロスドキュメントのサポートがない場合は、通常のリンクを維持します。ブラウザやデバイスごとに、サポート率、スキップ率、ready のレイテンシ、フレームレート、インタラクションレイテンシを測定します。トランジションによって見た目の表現が変わることはあっても、キャッシュ、権限、フォーム送信、ルーティングの正しさが損なわれてはなりません。
模範回答
ナビゲーションと状態の送信をアニメーションから独立させます。SPA では startViewTransition(update) を使用し、ready の後に必須の共有要素のみをアニメーション化します。マルチページアプリでは同一生成元の @view-transition を使用します。各共有要素には安定した一意のビジネス名が割り当てられ、非同期更新が失敗した場合はトランジションをスキップしつつ通常のエラー状態を表示します。
非対応のブラウザはネイティブの更新またはリンクを使用します。視覚効果の抑制を希望するユーザーには、アニメーションを提供しないか短縮されたアニメーションを提供します。ライフサイクルのタイムアウトでは skipTransition() を保持します。フォーカス、見出し、セマンティックな順序が視覚効果に依存することは決してありません。サポート状況、スキップ、インタラクションレイテンシを監視し、カナリアリリース後に名前付き要素のセットを拡張します。
よくある間違い
- 間違い → 配列のインデックスを
view-transition-nameとして使用する。失敗する理由 → ソートによって古いノードと新しいノードが誤ってペアリングされる。修正方法 → 安定したビジネス ID を使用し、一意性を強制する。 - 間違い → フォームを送信する前に
finishedを待機する。失敗する理由 → アニメーションの失敗によってビジネスアクションがブロックされる。修正方法 → 最初に状態を確定し、見た目の表現のみをアニメーション化する。 - 間違い → SPA のパスのみを設計する。失敗する理由 → マルチページナビゲーションには JavaScript の呼び出し箇所がない。修正方法 → 同一生成元の
@view-transitionを使用し、通常のリンクを維持する。 - 間違い → 視覚効果抑制の設定を無視する。失敗する理由 → 影響を受けやすいユーザーがめまいや認知的負荷を経験する可能性がある。修正方法 → メディアクエリ内でアニメーションを短縮またはスキップする。
フォローアップの質問
失敗した更新コールバックはトランジションと状態にどのように影響を与えるべきですか?
更新コールバックをビジネス状態の境界として扱います。例外をキャッチし、ロールバックするかエラーを描画し、トランジションの Promise はアニメーションのライフサイクルのみに限定します。updateCallbackDone が拒否(reject)された場合は、skipTransition() を呼び出すかブラウザにデフォルトの表示を完了させます。エラーをもみ消してはいけません。
すべての要素を共有要素として名前付けしないのはなぜですか?
各名前は、古いツリーと新しいツリーの間で明確なペアを形成する必要があります。すべてに名前を付けると、キャプチャ、レイアウト、合成のコストが増加し、予期しない一致が発生する可能性があります。ユーザーが理解できる主要なオブジェクトのみに名前を付け、残りはルートのフェードまたはアニメーションなしを使用します。
トランジションがパフォーマンスを損なっていないことをどのように証明しますか?
ブラウザおよびデバイスごとに ready のレイテンシ、フレームレート、Long Tasks、First Input Latency、スキップ率を測定し、現実的なリストサイズでテストします。ローエンドデバイスで継続的なフレーム落ちが見られる場合は、アニメーションのスコープを縮小するか自動的にスキップし、ナビゲーション時間を変更しないように維持します。