代表的な面接トピック

フロントエンド面接:確実なエントリーアニメーションに CSS @starting-style をどう活用するか?

フロントエンド普通
Offer.cc 編集チーム公開日 更新日

質問

`display: none` で非表示になっているダイアログを、開く際にフェードおよびスケールインさせ、閉じる際にトランジションで消えるようにする必要があります。古いブラウザ、アクセシビリティ、prefers-reduced-motion の挙動を維持しつつ、CSS @starting-style、transition-behavior、および top-layer 機能をどのように使用しますか?

1. 質問とコンテキスト

管理画面に dialog 要素と popover メニューがあります。JavaScript でフレームごとにスタイルを変更することなく開始および終了のトランジションを実現する設計が求められています。古いブラウザでも利用可能な状態を維持し、視覚効果を抑制しているユーザーに強いアニメーションを強制してはなりません。@starting-style の境界、CSS とスクリプトの役割分担、フォールバックの挙動、および検証について説明してください。

2. 面接官が評価するポイント

  • 最初のスタイル更新時にはトランジションの比較対象となる以前の計算値が存在せず、@starting-style が開始点を提供するという理解。
  • エントリーとエグジット、離散的な display、top-layer 要素、および通常の opacity/transform トランジションの違いを区別できること。
  • フォーカス、Escape、セマンティクス、モーション設定をプラットフォームが処理し、スクリプトは状態変更のみを担当させること。
  • CSS を貼り付けるだけでなく、機能検出、キャンセルの競合、視覚効果の抑制、実ブラウザでの検証を設計できること。

3. 最初に明確にすべき質問

  1. それは dialog 要素、popover、あるいは通常のノードですか?top-layer とデフォルトのフォーカス挙動によって設計が変わります。
  2. ノードはエグジットアニメーションが終了するまで残る必要がありますか、それとも即座に操作不可になる必要がありますか?これにより display とイベントの境界が変わります。
  3. 古いブラウザでは静的な結果を表示すべきか、瞬時に切り替えるべきか、ピクセル単位で正確に一致させるべきですか?
  4. prefers-reduced-motion の場合、継続時間を短縮するべきか、移動をなくすべきか、それともすべてのトランジションを削除すべきですか?

4. 30秒での回答

HTML の状態とセマンティクスが表示状態、フォーカス、閉じる処理を制御し、CSS が可逆的な視覚トランジションを担当するようにします。@starting-style を使用して最初の可視更新前の不透明度と変形の値を指定し、allow-discrete または互換パスを display に使用してトランジション中に要素が描画可能な状態を維持します。エグジットには開始ルールだけでなく、明示的な最終状態と DOM のライフタイムが必要です。古いブラウザは即座に切り替わり、モーション低減ユーザーには短縮または無効化されたアニメーションが適用され、キーボード、スクリーンリーダー、競合のテストによって結果を検証します。

5. ステップごとの回答

ステップ 1: 状態とセマンティクスの境界を定義する

dialog.showModal()、popover の開いた状態、およびクラスの切り替えは異なるセマンティクスを持ちます。閉じたコントロールがフォーカスを受け取らないこと、開いたコントロールが適切なフォーカスターゲットを取得すること、Escape および cancel イベントが機能することを確認してください。アニメーションは状態を反映するものであり、アクセシビリティの状態を定義してはなりません。スクリプトはタイマーで可視性をシミュレートするのではなく、プラットフォーム API を呼び出すか状態属性を切り替えるべきです。

ステップ 2: エントリーの開始点を作成する

最初のスタイル更新時、ブラウザには以前のスタイルが存在しない可能性があり、通常のトランジションには起点が存在しません。初期値を @starting-style に設定します。

css
.dialog {
  opacity: 1;
  transform: scale(1);
  transition: opacity 180ms ease, transform 180ms ease;
}

@starting-style {
  .dialog[open] {
    opacity: 0;
    transform: scale(0.96);
  }
}

開始ルールは最初のスタイル更新に適用され、持続的な閉状態として機能するわけではありません。コンポーネントが再挿入された場合、エントリーが実行されるかどうかはそのライフサイクルと DOM 操作に依存します。すべての属性切り替えが開始ルールをトリガーすると想定しないでください。

ステップ 3: 離散プロパティとエグジットの処理

display は不透明度のように補間できません。トランジション中に要素を描画可能な状態に保ち、離散的なポイントで利用不可状態に切り替えます。サポートされているブラウザでは transition-behavior: allow-discrete を使用し、それ以外では即時切り替えのフォールバックを使用します。トランジションが完了するまで終了中のノードを保持するか、ネイティブコンポーネントにそのライフサイクルを任せます。固定の setTimeout で推測してはいけません。transitionend をプロパティごとにフィルタリングし、キャンセル、繰り返しのオープン、アンマウントを処理します。

ステップ 4: トップレイヤーとインタラクションの考慮

top layer のダイアログやポップオーバーは、スタッキング、バックドロップ、フォーカスの挙動を変更します。コンテンツと ::backdrop のトランジションを個別に定義し、外部クリック、Escape、スクロールロック、複数インスタンスをテストします。スクリプトは API 呼び出し、キャンセル、ビジネスロジックのコールバックを維持する必要があります。@starting-style は JavaScript のステートマシンの代わりにはなりません。

ステップ 5: モーション設定の尊重と検証

@media (prefers-reduced-motion: reduce) では、状態フィードバックを維持しながら、移動を削除するか、時間を短縮するか、トランジションを無効にします。初回オープン、再オープン、高速な切り替え、終了中のナビゲーション、スクリプトの失敗、古いブラウザ、キーボード、スクリーンリーダー、バックドロップ、フォーカスの復帰をテストします。スクリーンショットの確認だけでなく、キャンセル、中断されたトランジション、非表示ノードの残留、アクセシビリティのデグレを追跡します。

6. 模範解答

まずセマンティクスから始めます。ネイティブの dialog や popover API と少量のスクリプトが開状態、フォーカス、Escape を制御し、CSS はその状態を表現するだけです。通常のトランジションは初回更新時に以前のスタイルを持たないため、@starting-style が不透明度と変形の起点を提供します。display は離散的であるため、サポートされている離散トランジションの動作を使用するか、古いブラウザでは即座に切り替えます。タイマーで推測するのではなく、トランジションが終了するまで終了ノードを保持します。top-layer のコンテンツとバックドロップを個別に定義し、モーション低減、高速な切り替え、キーボード、スクリーンリーダーをテストします。

7. よくある間違い

  • 開始ルールを閉じた状態として扱うこと: 後続の切り替えで誤った状態になります。持続的な値、エントリー時の値、最終的な閉状態の値を分離してください。
  • 通常の不透明度トランジションを display に適用すること: 要素が早すぎるタイミングで消えるか、操作可能なままになります。離散的な挙動とフォールバックを使用してください。
  • setTimeout でノードを削除すること: ユーザー設定、キャンセル、フレーム落ちによってライフサイクルが同期しなくなります。有効なトランジションイベントまたはネイティブのライフサイクルを使用してください。
  • アニメーションにフォーカスとセマンティクスを制御させること: 支援技術は視覚的なタイミングに従ってしまいます。まず HTML/API の状態を確立してください。
  • モーション低減設定を無視すること: ユーザーの設定に反します。明確な状態フィードバックを維持しつつ、モーションを短縮または削除してください。

8. フォローアップの質問と回答

フォローアップ 1: なぜ通常のトランジションは最初のフェードインで不十分なのですか?

最初のスタイル更新前には比較対象となる以前の計算スタイルが存在しない可能性があるため、ブラウザは最終値を直接適用します。@starting-style はその起点を提供します。これはその後のすべての終了状態を定義するものではありません。

フォローアップ 2: display はどのようにエグジットに参加できますか?

トランジション中は要素を描画可能に保ち、離散的なポイントで display を切り替えます。サポートされている環境では allow-discrete を使用し、それ以外では即座に閉じるようにします。その際、閉じた要素がフォーカスやポインターの操作を受け取らないようにします。

フォローアップ 3: 高速に「開く-閉じる-開く」変更を行った場合、何が起こりますか?

前のトランジションが完了する前に状態が反転する可能性があります。古いアニメーション処理をキャンセルし、現在の状態を読み取って、最新の状態のみがコミットされるようにします。ノードの削除と再オープンを独立して行う2つのタイマーを実行してはいけません。交互のクリック、Escape、ナビゲーションによる破棄をテストしてください。

公開情報ソース

関連する質問