2309 字
12 分鐘

CSS Scroll Marker 怎麼做?用 ::scroll-marker 建立可存取輪播導覽

只有一排可以左右捲動的卡片時,讀者未必知道還有幾張、目前在哪一張,鍵盤使用者也可能找不到下一個內容。CSS Overflow 5 提供的 ::scroll-marker::scroll-marker-group::scroll-button(),可以把這些導覽控制交給瀏覽器產生。

先說限制:這組 API 目前仍被 MDN 標為 experimental、不是 Baseline,瀏覽器支援不能當成現成的 production 基線。適合把它當 progressive enhancement:不支援時保留一般的水平捲動或明確的 HTML 連結,支援時再取得原生 marker、按鈕與狀態管理。

直接答案是:先讓元素成為真正的 scroll container,再設定 scroll-marker-group: after,最後在每個代表內容的子元素上設定非 none::scroll-marker { content: ... } 沒有 marker group,或沒有 content,marker 就不會出現。

四個 CSS 元件各自負責什麼#

API放在哪裡負責什麼
scroll-marker-groupscroll container 的 property決定是否產生 marker group,以及 group 放在內容前或後;最新語法還能選 linkstabs 模式
::scroll-marker-groupscroll container 的 pseudo-element排版與裝飾瀏覽器產生的 marker 容器
::scroll-marker每個輪播項目的 pseudo-element產生指向該項目的導覽 link
::scroll-button(left/right)scroll container 的 pseudo-element產生前後捲動按鈕;瀏覽器負責按鈕狀態與捲動行為

::scroll-marker 不是普通的圓點背景。瀏覽器會把它當成指向來源元素的 anchor,並提供 :target-current 狀態;在支援的實作中,鍵盤與輔助技術也會取得相應的語意。這仍然需要你用真實內容測試,不能只看畫面上有沒有圓點。

先建立一個真正可捲動的輪播#

HTML 不需要為每一個 marker 另外建立按鈕。保留正常的 list 結構,並為項目提供清楚的內容:

<ul class="carousel" aria-label="產品特色">
<li>
<h2>快速搜尋</h2>
<p>用關鍵字找到需要的功能。</p>
</li>
<li>
<h2>離線可讀</h2>
<p>沒有網路時仍保留已載入內容。</p>
</li>
<li>
<h2>鍵盤友善</h2>
<p>不使用滑鼠也能完成導覽。</p>
</li>
</ul>

先讓沒有 marker 的版本也能工作:

.carousel {
display: flex;
gap: 1rem;
margin: 0;
padding: 0 1rem 1rem;
overflow-x: auto;
overscroll-behavior-inline: contain;
scroll-snap-type: inline mandatory;
scroll-behavior: smooth;
}
.carousel > li {
flex: 0 0 min(80vw, 24rem);
min-block-size: 12rem;
padding: 1.25rem;
list-style: none;
scroll-snap-align: start;
border: 1px solid #cbd5e1;
border-radius: 0.75rem;
}
@media (prefers-reduced-motion: reduce) {
.carousel {
scroll-behavior: auto;
}
}

這裡先使用 logical properties,讓輪播不被寫死在水平 writing mode;如果你的元件只支援由左至右的水平卡片,也可以改成 overflow-xscroll-snap-type: x mandatoryscroll-snap-align: start

scroll-marker-group 產生 marker#

接著在 scroll container opt in,並從每個 li 產生 marker:

.carousel {
scroll-marker-group: after;
counter-reset: carousel-card;
}
.carousel > li {
counter-increment: carousel-card;
}
.carousel::scroll-marker-group {
display: flex;
justify-content: center;
gap: 0.5rem;
padding-block: 0.75rem;
}
.carousel > li::scroll-marker {
content: counter(carousel-card);
inline-size: 0.75rem;
block-size: 0.75rem;
border: 1px solid currentColor;
border-radius: 50%;
color: #475569;
}
.carousel > li::scroll-marker:focus-visible {
outline: 2px solid currentColor;
outline-offset: 3px;
}
.carousel > li::scroll-marker:target-current {
background: currentColor;
}

最容易漏掉的是 scroll-marker-group: after。這個 property 的初始值是 none;只有 group 被產生後,來源元素的 ::scroll-marker 才有容器可以放入。另一個必要條件是 content 不能是 none,否則 marker 不會生成。

after 代表 marker group 會出現在內容之後,也會排在相應的 tab order 後面。若你把 group 用 CSS 視覺上移到內容前方,應改用 before,讓視覺順序與鍵盤順序一致。

目前的基本寫法 scroll-marker-group: after 是 navigation/links 方向。Chrome 154 beta 的說明也列出新的模式關鍵字:

/* 一般輪播、章節導覽:marker 是 links */
.carousel {
scroll-marker-group: after links;
}
/* 每次只把一個 panel 當成目前頁面:marker 是 tabs */
.feature-tabs {
scroll-marker-group: after tabs;
}

兩者不是外觀名稱,而是不同的互動與輔助技術語意:

模式語意與鍵盤行為適合情況
linksgroup 接近 navigation,marker 是 links;每個 marker 可成為 sequential tab stop,啟動後焦點移到目標產品卡片、文章章節、圖片導覽,所有內容都仍屬於同一份可捲動文件
tabsgroup 接近 tablist,marker 是 tabs;目前 marker 保留 tab stop,其他 marker 用方向鍵切換,非目前 panel 會從 accessibility tree 隱藏內容確實是互斥的 tab panel,讀者一次只應閱讀目前頁面

如果只是想做幾個進度圓點,不要為了外觀使用 tabs。把原本同時存在的文章或商品卡片標成互斥 panel,可能讓螢幕閱讀器使用者失去尚未選取的內容;這是語意選擇,不是 CSS 樣式切換。

兩個關鍵字的支援仍可能落後於規格,應把它視為增強層。若瀏覽器只接受單一的 after,先使用 links 方向的基本版本,再由 feature detection 決定是否啟用 tabs

加上 ::scroll-button() 前後按鈕#

marker 適合跳到指定項目,前後按鈕則適合觸控板、滑鼠或不想逐一點 marker 的讀者:

.carousel::scroll-button(left) {
content: "←" / "往前捲動";
}
.carousel::scroll-button(right) {
content: "→" / "往後捲動";
}
.carousel::scroll-button(*) {
inline-size: 2.25rem;
block-size: 2.25rem;
border: 1px solid #cbd5e1;
border-radius: 50%;
background: Canvas;
}
.carousel::scroll-button(*):disabled {
opacity: 0.4;
}
.carousel::scroll-button(*):focus-visible {
outline: 2px solid currentColor;
outline-offset: 3px;
}

Chrome 的 CSS carousel 說明指出,scroll button 預設大約移動 scroll area 的 85%;它不保證剛好只前進一張卡片。如果需求是每次只移動一個 snap item,可以在每個項目加上 scroll-snap-stop: always,再用實機測試快速連按、觸控拖曳和鍵盤操作的結果。

content 後面的 / "往前捲動" 是 accessible fallback text,不是畫面上一定要顯示的第二段文字。即使瀏覽器替你建立了按鈕與 link,也要保留清楚的焦點樣式與可理解的 label。

沒有 marker 時的 fallback#

這組 API 尚未成為跨瀏覽器基線,所以基礎版面必須在沒有 generated UI 時仍可用。需要更穩定控制語意或支援縮圖導覽時,直接提供 HTML anchor:

<nav class="carousel-fallback" aria-label="產品特色導覽">
<a href="#feature-search">快速搜尋</a>
<a href="#feature-offline">離線可讀</a>
<a href="#feature-keyboard">鍵盤友善</a>
</nav>

對應的 li 加上 id:

<li id="feature-search">...</li>
<li id="feature-offline">...</li>
<li id="feature-keyboard">...</li>

如果希望支援 marker 的瀏覽器不要看到兩份導覽,可以用 feature detection 隱藏 fallback:

@supports selector(.carousel::scroll-marker) and (scroll-marker-group: after) {
.carousel-fallback {
display: none;
}
}

不支援 selector()::scroll-marker 的瀏覽器會保留 fallback;這正是 progressive enhancement 的目標。若你需要從既有 <a> 元素建立 marker group,也可以研究 scroll-target-group,但它和瀏覽器生成的 ::scroll-marker 在語意與控制權上不同,不要未測試就混用兩套導覽。

@supports 把實驗性 CSS 隔離#

實際專案可以把增強層集中,讓基本 scroll container 更容易維護:

@supports selector(.carousel::scroll-marker) and (scroll-marker-group: after) {
.carousel {
scroll-marker-group: after;
}
.carousel::scroll-button(left),
.carousel::scroll-button(right) {
content: "";
}
.carousel > li::scroll-marker {
content: "";
}
}

不要只以 CSS.supports("scroll-marker-group", "after") 判斷全部功能已可用;property、pseudo-element、button 以及 linkstabs 模式可能分開實作。需要啟用進階模式時,分別測試對應 selector 和宣告,並保留 HTML fallback。

上線前要測什麼#

瀏覽器生成控制項可以減少手寫狀態,但不能代替元件 QA。至少檢查:

  • marker group 的視覺位置與 beforeafter 的鍵盤順序一致。
  • 用 Tab、Shift+Tab、方向鍵與 Enter 操作,焦點不會消失在 overflow 區域裡。
  • 讀屏軟體讀到的 link/tab label 能區分每個項目;不要只剩沒有意義的圓點。
  • :target-current 在手指拖曳、scroll button、marker 啟動後都能切換。
  • 觸控、滑鼠滾輪、觸控板與快速連按時沒有卡在兩個 snap point 之間。
  • prefers-reduced-motion: reduce 下不強迫 smooth scroll。
  • 不支援 API 的瀏覽器仍能讀到全部內容,並使用 HTML fallback 完成導覽。

如果輪播同時有固定 header,還要替 snap target 預留上方空間,可參考 CSS Scroll Snap 避開固定導覽列的設定。若你改用 JavaScript 自行跳到卡片,則要另外確認 scroll container,避免和 scrollIntoView 滾錯容器的排查方式 混成同一個問題。

常見問題#

Q: 為什麼 ::scroll-marker 完全沒有出現?#

A: 先確認來源元素的祖先是 scroll container,該容器有 scroll-marker-group: beforeafter,以及 marker 的 content 不是 none。如果條件都符合,仍要確認目前瀏覽器是否實作這項 experimental API。

A: after 是基本的 marker group 位置寫法,行為接近 links;after links 是明確指定 links 模式的最新語法。瀏覽器支援不一致時,先以 after 作為較保守的增強層,再對 linkstabs 做 feature detection。

Q: 有了 scroll marker 還需要 JavaScript 嗎?#

A: 基本的跳轉、目前狀態、前後捲動與鍵盤行為可以由支援的瀏覽器處理,因此不一定需要 JavaScript。但如果你需要循環輪播、遠端資料、複雜的縮圖、跨瀏覽器一致行為或分析事件,仍可能需要 JavaScript;請保留無 JavaScript 時可讀、可捲動的基礎版面。

Q: tabs 模式可以套在所有卡片輪播嗎?#

A: 不建議。tabs 對非目前 panel 的內容有更嚴格的 accessibility 行為,只有當內容真的互斥、讀者一次只應看到一個 panel 時才適合。一般產品卡片或文章導覽通常先使用 links 方向。

參考資料:

Chrome for Developers:Carousels with CSS

Chrome for Developers:Chrome 154 beta

MDN:::scroll-marker pseudo-element

MDN:scroll-marker-group CSS property

MDN:scroll-target-group CSS property

CSS Scroll Marker 怎麼做?用 ::scroll-marker 建立可存取輪播導覽
https://laplusda.com/posts/css-scroll-marker-carousel-guide/
作者
Zero
發佈於
2026-09-11
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。