1. 題目與適用場景
管理後台有一個 dialog 元素和一個 Popover 選單。設計要求開啟、關閉都能轉場,但元件不能依賴 JavaScript 每幀改樣式;舊瀏覽器仍要可用,減少動效使用者不應看到強制動畫。請說明 @starting-style 的適用邊界、CSS 與腳本職責、回退與驗證。
2. 面試官考察點
- 是否知道首次樣式更新沒有可供 transition 的舊計算樣式,
@starting-style用來提供起點。 - 是否能區分進入與離開、離散屬性
display、top layer 元素與普通 opacity/transform 轉場。 - 是否會把腳本只用於狀態切換,把焦點、Escape、語意與動畫偏好交給正確層處理。
- 是否設計支援偵測、取消競態、減弱動效與真實瀏覽器驗證,而非只貼一段 CSS。
3. 回答前需要釐清的問題
- 元件是 dialog、Popover 還是普通節點?top layer 與預設焦點行為會改變方案。
- 需要關閉動畫後再移除元素,還是關閉後立即不可互動?這決定
display與事件邊界。 - 舊瀏覽器要保留靜態顯示、立即切換,還是必須視覺等價?
- 使用者開啟
prefers-reduced-motion時要縮短時間、取消位移,還是完全取消動畫?
4. 30 秒回答框架
我先讓 HTML 狀態與語意負責顯示、焦點與關閉,CSS 負責可逆的視覺轉場。進入時用 @starting-style 宣告元素首次顯示前的 opacity/transform 起點;離散的 display 切換使用 allow-discrete 或相應相容策略,確保元素在動畫期間仍可繪製。離場轉場不能只依賴一個起點規則,要明確最終狀態與 DOM 生命週期。舊瀏覽器立即切換,prefers-reduced-motion 降低或取消動畫,並用鍵盤、讀屏與競態測試驗證。
5. 分步驟深入解答
第一步:確定狀態與語意邊界
dialog.showModal()、Popover 的開啟狀態與普通 class 切換不是同一語意。先確保關閉時不可聚焦、開啟時焦點進入合理位置,Escape 與取消事件可用。動畫只反映狀態,不應成為可及性狀態來源;腳本應在狀態改變時觸發 API 或切換屬性,而不是用計時器模擬可見性。
第二步:建立進場起點
首次樣式更新時,瀏覽器可能沒有上一幀樣式,普通 transition 找不到起點。把初始值放進 @starting-style:
.dialog {
opacity: 1;
transform: scale(1);
transition: opacity 180ms ease, transform 180ms ease;
}
@starting-style {
.dialog[open] {
opacity: 0;
transform: scale(0.96);
}
}起點只用於首次樣式更新,不是持久的關閉狀態。若元件重新插入 DOM,是否重新觸發進場由生命週期與 DOM 操作決定,不能假設每次屬性切換都會執行起點規則。
第三步:處理離散屬性與離場
display 不能像 opacity 一樣連續插值。需要讓元素在轉場期間保持可繪製,並在結束時切換到不可見狀態;支援的瀏覽器可用 transition-behavior: allow-discrete,同時為不支援者提供立即切換。離場時要保留節點到 transition 完成,或由原生元件管理生命週期;不要用固定 setTimeout 猜測動畫是否結束。監聽 transitionend 時過濾屬性名稱,並處理取消、重複開啟與節點卸載。
第四步:相容 top layer 與互動
對話框和 Popover 進入 top layer 後,層疊上下文、backdrop 與焦點行為會改變。分別定義內容與 ::backdrop 的轉場,確認點擊外部、Escape、捲動鎖定與多個實例的狀態。腳本只保留 API 呼叫、取消與業務回呼,不能把 @starting-style 當成 JS 狀態機替代品。
第五步:尊重動效偏好並驗證
在 @media (prefers-reduced-motion: reduce) 中取消位移、縮短時間或直接關閉 transition,仍保留狀態回饋。測試首次開啟、重複開啟、快速切換、關閉期間導覽、腳本失敗、舊瀏覽器、鍵盤、讀屏、backdrop 與焦點回收。記錄動畫取消率、轉場中斷、不可見節點殘留與可及性回歸,而不是只檢查截圖。
6. 高品質示範回答
我會先區分元件語意:dialog或 Popover 的開啟、焦點與 Escape 由原生 API 和少量腳本負責,CSS 只呈現狀態。普通 transition 沒有首次樣式更新的舊起點,因此用@starting-style給開啟狀態提供 opacity 與 transform 起點;display這類離散屬性要使用支援的離散轉場能力或舊瀏覽器立即切換。離場必須保留節點到轉場完成,不能用固定計時器猜生命週期。對 top layer 內容與 backdrop 分別定義規則,並在 reduced-motion、快速切換、鍵盤與讀屏路徑驗證。
7. 常見錯誤
- 把起點規則當成關閉狀態 → 後續屬性切換找不到正確狀態 → 明確持久狀態、進場起點與最終關閉狀態。
- 給
display寫普通 opacity 轉場 → 元素提前不可繪製或仍可互動 → 使用離散轉場能力並定義相容路徑。 - 用
setTimeout移除節點 → 使用者改速、取消或丟幀時生命週期漂移 → 監聽有效 transition 事件或使用原生生命週期。 - 動畫控制焦點與語意 → 讀屏與鍵盤狀態依賴視覺時間 → 先保證 HTML/API 狀態,再加入視覺轉場。
- 忽略 reduced motion → 使用者偏好被違反 → 縮短或取消動畫,保留清楚狀態變化。
8. 追問及應對
追問一:為什麼普通 transition 不足以實作首次淡入?
首次樣式更新前沒有可比較的舊計算樣式,瀏覽器可能直接套用最終值。@starting-style 提供這個時刻的起點;它只描述首次更新,不等於所有後續關閉狀態。
追問二:關閉時如何讓 display 參與轉場?
讓元素在轉場期間保持可繪製,並在離散時間點切換 display;支援 allow-discrete 時使用它,舊瀏覽器立即關閉。無論哪條路徑,都要確保關閉後不能繼續取得焦點或滑鼠互動。
追問三:快速連續開啟和關閉會怎樣?
狀態可能在上一條 transition 完成前反轉。取消舊動畫、讀取目前狀態並讓新狀態成為唯一提交者;不要用兩個並行計時器分別移除與開啟節點。測試交替點擊、Escape 與導覽卸載。