置頂標頭是常見的 UI 模式,但通常需要在「置頂」時變更外觀,以維持可讀性或節省空間。傳統解決方案需要 JavaScript 捲動事件監聽器,這可能會導致效能問題。現代 CSS 可讓您使用捲動狀態查詢,以原生方式處理這項作業。
導入步驟
1. 建立固定容器
您需要一個容器,做為固定元素和捲動狀態查詢的容器。
<!-- Wrap in a section to define the containment area for the sticky element -->
<div class="section">
<div class="sticky-container">
<div class="sticky-header">
Section Header
</div>
</div>
<div class="content">
<!-- Content goes here -->
</div>
</div>
2. 套用 CSS 以設定固定行為和容器類型
請在 position: sticky 元素上設定 container-type: scroll-state,而不是在可捲動的上階元素上設定。請務必指定 container-name,因為這些查詢可能會巢狀,且未命名的容器會發生衝突。您也可以搭配大小查詢,例如 container-type: scroll-state inline-size。
.sticky-container {
position: sticky;
top: 0;
/* MANDATORY: Always specify a container name to avoid collisions if CQs are nested */
container-type: scroll-state;
container-name: section-header;
z-index: 10;
}
.sticky-header {
/* Base styles for the header */
background: #f0f0f0;
padding: 20px;
transition: background-color 0.3s, box-shadow 0.3s;
}
重要事項:捲動狀態容器的子系會查詢捲動狀態。@container 查詢中的樣式不會套用至 .sticky-container 本身,而是套用至其子項 (例如 .sticky-header)。您無法使用容器元素本身的捲動狀態查詢,為容器元素本身設定樣式。
3. 鎖定卡住狀態
使用 @container scroll-state(...) 查詢,在標頭固定時套用樣式。
/* Apply styles when the named container is stuck at the top */
@container section-header scroll-state(stuck: top) {
.sticky-header {
background: #0056b3;
color: white;
box-shadow: 0 4px 6px rgb(0 0 0 / 0.15);
}
}
提示:您也可以使用 stuck: inset-block-start 或 stuck: inset-inline-start 等邏輯屬性,查詢與流程相關的邊緣 (而非實體邊緣),進一步支援國際化 (i18n) 和由右至左 (RTL) 版面配置。
維度變更注意事項
如果未停用捲動錨定功能,在固定時變更會影響版面配置的屬性 (高度、邊框間距、字型大小),可能會導致視覺閃爍。在固定元素下方的內嵌內容上,捲動錨定功能會調整捲動偏移量,以補償版面配置變更,進而將元素推回固定位置,並觸發震盪。
如要避免這種情況,請停用固定元素的父項捲動錨定功能:
.section {
overflow-anchor: none;
}
請將 overflow-anchor: none 套用至 position: sticky 元素的直接父項,而非 :root 或 body,否則會停用整個網頁的捲動錨定功能。這樣一來,您就能自由變更固定狀態下的任何屬性,包括盒模型屬性和轉換。
請勿:使用 overflow-anchor: none 處理固定在底部的元素。捲動錨定功能只會補償目前捲動位置上方的版面配置位移,因此不會影響底部固定的元素。避免在底部固定標頭的固定狀態下變更方塊模型屬性。
備用策略
捲動狀態查詢是一種漸進增強功能。在不支援 container-type: scroll-state 的瀏覽器中,標頭仍會固定在頂端 (因為 position: sticky),但不會有視覺轉換效果。在多數情況下,建議採用這個做法。
如果固定效果並非必要,但 position: sticky 沒有不同樣式會比完全沒有固定效果更差,您也可以將 position: sticky 和相關宣告包裝在 @supports 查詢中:
.sticky-container {
@supports (container-type: scroll-state) {
position: sticky;
top: 0;
container-type: scroll-state;
container-name: section-header;
z-index: 10;
}
}
提示:如果「固定」樣式需要不同的背景顏色才能提高可讀性,建議您設定版面配置,讓預設樣式適用於所有位置 (漸進增強)。如果必須使用備用方案,可以在 @supports (container-type: scroll-state) 查詢中限制 position: sticky 行為本身。
如果視覺轉換對設計至關重要 (例如,固定狀態會引入背景或壓縮,否則內容無法閱讀),您可以使用 IntersectionObserver 實作強大的 JavaScript 備用方案。您必須在 .is-stuck 類別下複製 CSS 樣式。下列一般 polyfill 會檢查 getComputedStyle,向上遍歷並找出正確的捲動容器 (預設為可視區域),與 @container scroll-state(stuck: top) 的行為相符:
function getScrollParent(node) {
if (node == null || node === document.body || node === document.documentElement) {
return null; // default to viewport
}
if (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth) {
const overflow = getComputedStyle(node).overflow;
if (overflow !== 'visible' && overflow !== 'clip') {
return node;
}
}
return getScrollParent(node.parentNode);
}
document.querySelectorAll('.sticky-container').forEach(container => {
const root = getScrollParent(container);
const topOffset = parseFloat(getComputedStyle(container).top) || 0;
const observer = new IntersectionObserver(
([e]) => {
// Toggle the fallback class on the sticky header container
e.target.classList.toggle('is-stuck', e.intersectionRatio < 1);
},
{
root: root,
threshold: [1],
rootMargin: `-${topOffset + 1}px 0px 0px 0px`
}
);
observer.observe(container);
});
注意:這個一般 IntersectionObserver 模式也可以做為 scroll-state(scrollable) 查詢的 Polyfill。