1. 題目與適用場景
一個對話框透過 display: none 隱藏,產品希望它淡入、淡出並在動畫結束後離開版面。團隊只給 opacity 加上 transition,結果開啟時沒有進入動畫,關閉時元素立即消失。請解釋離散屬性的限制,設計 CSS 方案,並說明瀏覽器降級、焦點管理和 prefers-reduced-motion。
2. 面試官考察點
- 是否知道
display、content-visibility等離散屬性不會產生連續中間值。 - 是否能正確使用
transition-behavior: allow-discrete或 shorthand,而不是把它誤當作動畫時長。 - 是否理解進入和退出時的翻轉時刻不同,且
@starting-style負責建立初始樣式。 - 是否把相容性、無障礙、焦點、減少動效和真實渲染測試納入答案。
3. 回答前需要釐清的問題
- 元件是普通元素、原生 dialog 還是 popover 頂層元素?
- 隱藏後是否必須離開版面、不可聚焦,還是只需跳過繪製?
- 支援的瀏覽器範圍和無法使用離散過渡時的降級是什麼?
- 動畫期間焦點、Escape、點擊遮罩和減少動效偏好如何處理?
4. 30 秒回答框架
display 是離散屬性,預設不會在兩個值之間產生 transition。用 transition-behavior: allow-discrete 明確允許離散屬性參與過渡,並同時給 opacity 或 transform 設定非零時長。隱藏到顯示時要讓元素在整個進入動畫期間可見,顯示到隱藏時要讓它保持可見直到動畫結束;@starting-style 可提供首次插入時的起始值。舊瀏覽器保留 opacity/transform 的連續動畫,並接受瞬時 display 切換;同時管理焦點、減少動效和可存取狀態。
5. 分步驟深入解答
第一步:區分連續與離散屬性
opacity 和 transform 可以計算連續中間值;display 只有取值切換。沒有 allow-discrete 時,瀏覽器會在狀態變化時立即切換 display,所以關閉動畫還沒開始元素就被移除,開啟動畫也沒有可見的初始幀。transition-behavior 只決定是否允許離散屬性過渡,仍需要 transition-property 和非零 transition-duration。
第二步:為普通面板建立雙向過渡
先寫連續屬性的相容宣告,再寫帶 allow-discrete 的宣告。退出時 display 應在 100% 附近翻轉,確保內容保持可見;進入時應儘早翻轉到可見值。不要只寫 transition-behavior 而忘記 opacity 或 transform 的目標狀態。
.panel {
opacity: 0;
transform: translateY(-0.5rem);
display: none;
transition: opacity 180ms ease, transform 180ms ease;
transition: opacity 180ms ease, transform 180ms ease, display 180ms allow-discrete;
}
.panel[data-open="true"] {
opacity: 1;
transform: translateY(0);
display: block;
}第三步:處理首次進入和 popover
動態插入或 popover 首次開啟時,元素可能沒有舊的可見樣式可供比較。@starting-style 可宣告開啟狀態的起始 opacity 和 transform。原生 popover 還會進入 top layer,必須把 display 和 overlay 納入離散過渡,否則退出時會直接從頂層移除。不同瀏覽器對這些組合的支援要用目標版本實測。
第四步:設計降級與能力偵測
舊瀏覽器會忽略未知的 allow-discrete,但應仍保留基礎 opacity/transform 過渡。可以用 @supports (transition-behavior: allow-discrete) 包住增強規則,避免把整組基礎樣式放進去。降級狀態必須仍可開啟、關閉、聚焦和讀取,不應因為動畫功能缺失而把內容永久隱藏。
第五步:無障礙、效能與驗證
動畫不等於可存取狀態。關閉後的對話框需要同步 hidden、aria-hidden 或原生 dialog 狀態,並把焦點返回觸發按鈕;開啟時把焦點移入並支援 Escape。prefers-reduced-motion: reduce 下縮短或移除時長。驗證時檢查版面、繪製、鍵盤焦點、螢幕閱讀器、快速重複開關和中斷動畫,並記錄低階裝置的合成與版面成本。
6. 高品質示範回答
display是離散屬性,不能像 opacity 一樣產生連續中間值。我的基礎規則先過渡 opacity 和 transform,再用transition-behavior: allow-discrete把 display 納入增強路徑;進入時先切到可見值,退出時保持可見直到動畫末尾。首次開啟用@starting-style提供起始幀,popover 還要處理 top layer 的 display 和 overlay。舊瀏覽器忽略增強宣告但仍有基礎動畫。程式碼之外,我會同步焦點、Escape、隱藏語義和 reduced-motion,並用多瀏覽器和鍵盤測試確認不會閃爍或把內容留在可聚焦狀態。
7. 常見錯誤
- 只給 opacity 寫 transition → display 立即切換 → 使用 allow-discrete 並保留連續屬性動畫。
- 把
transition-behavior當作 duration → 離散屬性仍無動畫 → 同時宣告 property 和非零 duration。 - 關閉時 display 先變 none → 使用者看不到淡出 → 讓離散翻轉與退出時長對齊。
- 忽略首次插入 → 第一次開啟沒有進入動畫 → 用
@starting-style建立初始狀態。 - 只測滑鼠和現代瀏覽器 → 鍵盤、讀屏或舊瀏覽器狀態錯誤 → 加入 supports 降級、焦點和 reduced-motion 驗證。
8. 追問及應對
追問一:為什麼關閉時 display 要在動畫結束才切換?
如果提前切換成 none,元素會離開版面和繪製樹,opacity 的剩餘時長沒有可見內容。讓它保持可見到 100% 附近,才能完成淡出,再進入隱藏狀態。
追問二:allow-discrete 會讓 display 逐幀插值嗎?
不會。它允許離散值參與 transition,但值仍在特定時間點翻轉;連續視覺應由 opacity、transform 等屬性提供。
追問三:怎樣讓舊瀏覽器仍可用?
先宣告不包含 allow-discrete 的基礎 transition,再用 @supports 或後續宣告增強。功能缺失時接受 display 瞬時切換,但保持按鈕、焦點和隱藏語義正確。
追問四:如何處理 reduced-motion?
在 prefers-reduced-motion: reduce 下把 duration 降到很短或設為零,同時保留狀態同步、焦點移動和可關閉操作;不要只隱藏動畫而留下不可見但可聚焦的元素。