代表的な面接トピック

フロントエンド面接:View Transition APIを安全に使用するにはどうすればよいか?

フロントエンド難しい
Offer.cc 編集チーム公開日 更新日

質問

あるプロダクトで、SPAのルート変更および画像詳細ビューの展開にView Transition APIのエフェクトを導入したいと考えています。実装方法と、古いブラウザ、prefers-reduced-motion、非同期データ、フォーカス、アニメーションの失敗、パフォーマンスフォールバックへの対応方法を説明してください。

プロンプトとスコープ

あるプロダクトで、SPAのルート変更および画像詳細ビューの展開にView Transition APIのエフェクトを導入したいと考えています。実装方法と、古いブラウザ、prefers-reduced-motion、非同期データ、フォーカス、アニメーションの失敗、パフォーマンスフォールバックへの対応方法を説明してください。

これはデモのアニメーションではなく、APIの境界とプログレッシブエンハンスメントをテストするものです。MDNではdocument.startViewTransition()を同一ドキュメント更新のエントリポイントとして定義しており、コールバックが完了した後に遷移が進行するとしています。また、クロスドキュメントの遷移には同一生成元(same origin)とCSSの@view-transitionも必要です。アニメーションは状態変化に資するものでなければならず、ユーザビリティを妨げてはなりません。

面接官がテストしていること

第一に、同一ドキュメントSPA、要素スコープ、クロスドキュメントの各遷移を区別できるか? 第二に、未サポートAPI、拒絶(reject)されたコールバック、視覚効果の抑制(reduced motion)、ページ離脱を処理できるか? 第三に、スナップショットアニメーションの背後に隠すのではなく、フォーカス、セマンティックなDOM、スクロール、実際のインタラクションを維持できるか?

回答前に確認すべき質問

  • どの境界が遷移していますか? 同一ドキュメントのDOM更新、単一要素、それともドキュメント間のナビゲーションですか?
  • 更新に非同期データが含まれていますか? 何を最初に準備する必要があり、最大待機時間はどれくらいですか?
  • 古いブラウザで許容される体験はどのようなものですか? 同じ状態更新がアニメーションなしで動作する必要があります。
  • どのユーザーがモーションを減らす、または無効化していますか? prefers-reduced-motionとアプリの設定はどのように組み合わせますか?
  • ページ状態はどのように復元されますか? フォーカス、スクロール、フォーム入力、戻るナビゲーションは?
  • パフォーマンスバジェットはどのようになっていますか? スナップショットのスコープ、所要時間、低スペック端末、同時遷移は?

30秒の回答フレームワーク

「状態の更新とアニメーションを分離します。サポートされている場合は、同期的なDOM更新をstartViewTransitionでラップし、サポートされていない場合は同じ更新を直接実行します。ルートまたはコンポーネント内で非同期データを準備し、コールバックは準備できた状態のコミットに集中させ、readyfinished、およびskipTransitionを監視します。名前付きビューを使用してスコープを制限し、prefers-reduced-motionのもとではモーションを短縮または無効化し、フォーカスとスクロールを復元して、アニメーションの失敗がインタラクションをブロックしないようにします。低スペック端末での長時間のタスク、スナップショット数、完了時間、フォールバック率を検証します。」

ステップごとの詳細解説

ステップ1:状態更新とアニメーションのゴールを定義する

ページ全体を無差別にフェードさせるのではなく、リストカードから詳細ページへ、またはフィルターされた結果から新しいリストへなど、アニメーションが接続する2つの状態を指定します。更新はアニメーションなしでも正しく動作しなければなりません。アニメーションはエンハンスメントレイヤーです。各ナビゲーションタイプに対して許可される遷移名とフォールバック動作を定義します。

ステップ2:サポートを検出し、同期フォールバックを維持する

呼び出す前にdocument.startViewTransitionを確認します。サポートしていないブラウザでは同じ更新関数を直接実行し、ビジネスロジックを重複させないようにします。新しいブラウザのサポートはすべての古いデバイスをカバーしているわけではないため、MDNの互換性ガイダンスで求められている通り、フォールバックパスを維持します。

ステップ3:コールバックと非同期データを制限する

updateCallbackは古いビューのスナップショットの後に実行されます。その返されたPromiseは次のフレームの前に解決される必要があります。可能な限り開始前にデータをフェッチしてください。コールバックが待機しなければならない場合は、タイムアウトを設定し、遷移をスキップできるようにします。拒絶されたコールバックは通常のビジネスエラー処理に従い、中途半端に更新されたページを公開してはなりません。

js
const update = () => renderState(nextState);

if (!document.startViewTransition || reduceMotion) {
  update();
} else {
  const transition = document.startViewTransition(update);
  transition.finished.catch(() => {
    // Animation failure does not roll back the completed state update.
  });
}

ステップ4:名前付きビューでスナップショットを制限する

実際に位置を共有する要素にのみ一意のview-transition-nameを付与します。繰り返されるリストコンポーネントは、曖昧さなしに1つの名前を共有することはできません。動的リストの場合、更新前に古い名前をクリアし、更新後に安定した一意の名前を割り当てます。

ステップ5:視覚効果の抑制とアクセシブルな動作を実装する

prefers-reduced-motion: reduceをリッスンし、所要時間をゼロまたはゼロ近くに設定して、状態変化を維持します。キーボードフォーカスを隠したり、色だけで結果を伝えたりしないでください。更新後、新しいビューの見出しまたは同等の要素にフォーカスを移動します。web.devは、全画面のビュー遷移アニメーションが前庭機能障害を持つ人々に不快感を与える可能性があると警告しています。

ステップ6:SPA、要素、クロスドキュメントの違いを処理する

SPAはDOM更新をdocument.startViewTransitionでラップします。要素スコープの遷移は呼び出し要素とその子孫に影響します。クロスドキュメントのナビゲーションには、同一生成元と両方のドキュメントでの@view-transitionオプトインが必要です。ドキュメントのライフサイクル全体を通じて同一ドキュメントのJSコールバックが存在すると想定しないでください。

ステップ7:スキップ、並行性、失敗パスを設計する

連続したクリックに対しては、古いナビゲーションをキャンセルまたは統合し、遷移が同じノードを取り合わないようにします。スタックしたアニメーションを終了するには、skipTransition()またはタイムアウトを使用します。ビジネス状態が更新された後は、視覚的な失敗によって二重送信、重複リクエスト、または誤ったロールバックが発生してはなりません。

ステップ8:パフォーマンスと実際のユーザー体験を検証する

開始から終了までの時間、スキップ率、ロングタスク、CLS、入力遅延、低スペック端末のエラーを監視します。大規模リスト、低速ネットワーク、拒絶された非同期処理、素早い「戻る」ナビゲーション、スクリーンリーダー、視覚効果の抑制をテストします。スナップショットとアニメーションによって実際のインタラクションの待機時間が長くなっていないことを確認します。

トレードオフと境界

トレードオフ1:全画面遷移かローカル遷移か

全画面遷移はシンプルですが、スナップショットのコストが高くなり、フォーカスやスクロールに干渉する可能性があります。ローカル遷移には安定した名前と正確なコンポーネント境界が必要であり、頻繁なインタラクションや大きなページに適しています。

トレードオフ2:データを待つか、即座に遷移するか

待機すると新旧コンテンツの不一致を回避できますが、応答時間が増加します。プリフェッチを優先してください。そうでない場合は明示的な読み込み状態を表示します。遷移はローディングフィードバックの代わりにはなりません。

トレードオフ3:カスタムアニメーションかブラウザデフォルトか

デフォルトの方が安全で保守が容易です。擬似要素のアニメーションをオーバーライドするのは、明確なナビゲーションセマンティクスと検証バジェットがある場合のみにし、視覚効果の抑制に対しても同様に機能する静的状態を提供してください。

障害訓練と進化計画

訓練1:サポートされていないブラウザまたは拒絶されたコールバック

サポートされていないブラウザで直接更新し、Promiseを拒絶して、ページ状態が正しいままであることを検証します。診断情報のみをログに記録し、継続的なインタラクションを決してブロックしないようにします。

訓練2:素早いナビゲーションと非同期の競合

最初のリクエストを遅くしながら、2つのリンクを素早くクリックします。古い遷移が現在の状態を上書きできないこと、フォーカスが現在のページに当たること、重複した副作用リクエストが送信されないことを確認します。

訓練3:視覚効果の抑制と低スペック端末

システムでモーションの抑制を有効にし、CPUに制約のあるデバイスで大規模リストの遷移を実行します。入力遅延とレイアウトの安定性がバジェット内に収まっている一方で、アニメーションが無効化または短縮されていることを確認します。

よくある間違いとフォローアップ

間違い1:フォールバックパスがない

サポートされていないAPIでも、同じDOMまたはルートの更新を生成する必要があります。プログレッシブエンハンスメントにおいて、ビジネス状態をアニメーションの成否に結びつけてはなりません。

間違い2:コールバック内でネットワークをいつまでも待つ

スナップショット後の長い待機は古いページをフリーズさせます。事前にデータを準備するか、タイムアウトを設定してアニメーションをスキップしてください。

間違い3:多くの要素に1つの共通名を付ける

名前が重複すると照合の曖昧さや余分なスナップショットが発生します。実際に移動する要素にのみ名前を付け、その名前を一意かつ安定したものに保ちます。

間違い4:フォーカスとスクロールを無視する

視覚的な遷移が完了しても、キーボードユーザーが適切な場所に戻るわけではありません。フォーカス、スクロール、およびセマンティックな見出しを明示的に復元してください。

間違い5:視覚効果の抑制を機能の削除として扱う

ユーザーが求めているのはモーションの削減であり、状態や情報の削減ではありません。同じインタラクションを維持し、アニメーションの表現のみを変更します。

間違い6:アニメーション失敗時にビジネス状態をロールバックする

finishedの拒絶は通常、視覚ステージの失敗を表します。すでに成功した状態更新を再送信したりロールバックしたりしないでください。

公開情報ソース

関連する質問