題幹與適用場景
面試題:頁面有吸頂標題、橫向 snap 卡片和「還有內容可捲動」提示。你會如何判斷何時改變樣式?請比較 CSS scroll-state 查詢與 JavaScript 監聽 scroll 事件的方案,並說明瀏覽器不支援時怎麼辦。
適用邊界是 CSS 只負責宣告式樣式變化,例如吸頂時加陰影、元素 snap 後高亮、仍可向下捲動時顯示提示。業務狀態、複雜手勢、精確捲動進度和需要向伺服器回報的事件仍應由 JavaScript 處理。MDN 將 scroll-state 查詢分為 scrollable、scrolled、snapped 和 stuck 四類。
面試官在考察什麼
面試官看重你能否先按狀態類型選 API,再處理支援矩陣和無障礙限制。好的回答會指出查詢容器必須設定 container-type,回應樣式寫在其後代元素上;也會說明不要把 CSS 狀態查詢當成通用事件系統。
普通回答只背出 @container 語法。好的回答還會解釋何時仍需 JavaScript、如何用 @supports 降級、如何避免動效違反 prefers-reduced-motion,以及如何用真實 sticky、snap 和溢出情境驗證。
回答前需要澄清的問題
先確認目標是樣式回饋還是業務邏輯。如果只是陰影、顏色、提示顯示,CSS 足夠;如果需要記錄使用者捲動方向、觸發網路請求或驅動複雜狀態機,就需要 JavaScript。
再確認狀態來源:元素是否真的使用 position sticky,清單是否設定 scroll-snap-type,捲動容器是否有可見溢出。缺少這些前提,對應查詢會一直回傳 false。
最後確認瀏覽器基線和動效要求。Chrome for Developers 說明 Chrome 133 引入 scroll-state 容器查詢;生產方案應先定義不依賴新特性的預設樣式,再把增強規則包進 supports,並尊重 reduced motion。
30 秒回答框架
可以這樣回答:
「我先把需求分成樣式狀態和業務事件。吸頂、snap 和是否可捲動屬於瀏覽器能判斷的狀態,我會給相關元素設定 container-type: scroll-state,用 @container ... scroll-state(...) 修改後代樣式。若需要統計、精確進度或跨元件業務動作,仍用 JavaScript。預設樣式先可用,再用 @supports 漸進增強,並在 prefers-reduced-motion 下關閉非必要動畫。最後用真實捲動、snap、溢出和不支援瀏覽器驗證。」
分步驟深入解答
先建立 scroll-state 查詢容器
被查詢的元素要宣告 scroll-state 容器,可選擇命名以避免頁面有多個容器時誤匹配:
.sticky-heading {
position: sticky;
top: 0;
container-type: scroll-state;
container-name: heading;
}回應樣式必須放在容器的後代,不能把同一元素既作為查詢容器又作為回應目標。Chrome 文件明確展示了這種代理層關係。
按狀態選擇 descriptor
stuck 判斷 sticky 元素是否貼住邊緣;snapped 判斷元素是否即將吸附到 snap 容器;scrollable 判斷指定方向是否還有可捲動溢出;scrolled 判斷最近一次捲動方向。四者回答的問題不同,不能用一個狀態代替另一個。
例如吸頂時給內部導覽加陰影:
@container heading scroll-state(stuck: top) {
.nav {
box-shadow: 0 5px 12px rgb(0 0 0 / 18%);
}
}把樣式目標放到後代
如果把背景直接改在帶 container-type 的元素上,程式結構容易違反查詢容器與目標分離的規則。保留一個內部 wrapper,讓它回應狀態;這樣元件可以重複使用,也更容易測試狀態切換。
用 scrollable 做可發現性提示
對橫向卡片可以在仍能向 inline-end 捲動時顯示漸層提示:
.carousel {
container-type: scroll-state;
container-name: cards;
overflow-x: auto;
scroll-snap-type: x mandatory;
}
@container cards scroll-state(scrollable: inline-end) {
.next-hint {
opacity: 1;
}
}提示必須是裝飾或輔助發現,不要只靠顏色傳達「還有內容」;保留鍵盤可捲動、焦點可見和文字標籤。
用 snapped 回饋目前卡片
snapped 只在存在 scroll snap 祖先和有效 snap 軸時有意義。高亮目前卡片可以改善視覺回饋,但不要把它當作可靠的業務選擇提交訊號。真正的選取事件仍應由可存取控制項和 JavaScript 狀態管理。
用 scrolled 處理方向回饋
頂部工具列可依最近捲動方向顯示或隱藏,但方向回饋容易造成認知負擔。預設先保持工具列可存取,有明確收益時才透過 scrolled: block-start 或 block-end 改變位置,並加上過渡。
漸進增強與降級
基礎樣式應在不支援 scroll-state 的瀏覽器中仍可操作:
.nav {
box-shadow: none;
}
@supports (container-type: scroll-state) {
.sticky-heading {
container-type: scroll-state;
}
@container scroll-state(stuck: top) {
.nav {
box-shadow: 0 5px 12px rgb(0 0 0 / 18%);
}
}
}若體驗必須在舊瀏覽器一致,可用 IntersectionObserver 或 ResizeObserver 做有限 fallback,但不要在 fallback 中複製整套捲動事件迴圈。
動效和驗證
Chrome 建議把非必要動效放在 prefers-reduced-motion: no-preference 內。驗證時分別測試頂部、底部、無溢出、鍵盤操作、觸控捲動、snap 中間態、縮放和不支援查詢的瀏覽器;檢查狀態變化不會阻擋焦點或內容。
高品質示範回答
「我會先問這是樣式回饋還是業務事件。對吸頂陰影、snap 高亮和捲動提示,我會給 sticky 或 snap 元素設定 container-type: scroll-state,依需求使用 stuck、snapped 或 scrollable 查詢,讓後代元素回應。若要回報捲動、計算進度或提交選擇,仍由 JavaScript 負責。預設 CSS 先保證可用,再用 @supports 增強;不支援時保持無陰影或始終顯示提示,並在 reduced-motion 下減少位移。測試涵蓋真實溢出、snap、鍵盤和舊瀏覽器。」
常見錯誤
把 scroll-state 當成 scroll 事件
錯誤表現:用查詢直接觸發埋點、請求或業務提交。失敗原因:CSS 查詢只表達樣式條件,沒有事件載荷和可靠業務時序。修正方法:樣式由 CSS 管理,業務動作由語意控制項和 JavaScript 管理。
忘記宣告查詢容器
錯誤表現:直接寫 @container scroll-state(stuck: top),卻沒有 container-type: scroll-state。失敗原因:瀏覽器沒有可評估的狀態來源。修正方法:先檢查 sticky、snap 或溢出前提,再宣告命名容器。
把回應規則寫在容器本身
錯誤表現:同一元素同時承擔 container-type 和狀態樣式目標。失敗原因:查詢容器針對後代,結構會讓規則不匹配或難以維護。修正方法:增加內部 wrapper,把狀態回饋放在後代。
用顏色取代可存取回饋
錯誤表現:只改變顏色表示目前 snap 卡片或還有更多內容。失敗原因:顏色不足以傳達狀態,也可能影響低視力使用者。修正方法:提供文字、焦點、按鈕或可操作的捲動提示。
無視支援與動效邊界
錯誤表現:假設所有瀏覽器都支援,並在捲動時強制位移。失敗原因:舊瀏覽器體驗退化,動效可能引發不適。修正方法:用 @supports 做增強、預設樣式保持可用,並尊重 reduced-motion。
追問及應對
如果需要精確捲動百分比呢?
scroll-state 只能回答離散狀態,不能提供連續百分比。使用 JavaScript 讀取捲動範圍並節流更新,但把讀取、計算和寫入分離,避免每個 scroll 事件同步觸發布局。
如果 sticky 查詢一直是 false 呢?
逐項檢查 sticky 元素的 containing block、top 或 inset 是否設定、祖先是否有不合適的 overflow,以及元素是否真的進入黏滯區。確認前提後再檢查 container-type 和後代選擇器。
如果 snap 高亮閃爍呢?
檢查 snap 軸、scroll-snap-align 和目標尺寸,避免把過渡寫在會頻繁改變布局的屬性上。高亮只表示視覺狀態,保留明確的焦點和選取狀態。
如果舊瀏覽器必須有相同效果呢?
先定義可用的靜態樣式,再用小範圍 IntersectionObserver 或 ResizeObserver 補充關鍵回饋。不要複製一套複雜的捲動監聽邏輯;若增強成本超過收益,接受無陰影或始終可見提示。
如果使用者開啟 reduced motion 呢?
保留資訊層級,不強制位移或縮放。可以只改變陰影、邊框或靜態顏色,並讓 prefers-reduced-motion 覆蓋過渡和動畫規則。