建立 CSS 輪播

CSS overflow 模組定義了一些特性,可以用來建立靈活且無障礙的純 CSS 輪播,它帶有瀏覽器生成且可由開發者設定樣式的滾動按鈕和滾動標記。本指南將解釋如何使用這些特性來建立一個輪播。

輪播是 Web 上的一個常見功能。它們通常以滾動內容區域的形式出現,其中包含多個專案,例如演示幻燈片、廣告、頭條新聞或關鍵產品特性。

使用者可以透過點選或啟用導航按鈕或透過滑動來瀏覽這些專案。導航通常包括:

滾動按鈕

通常是“上一個”和“下一個”按鈕或連結。

滾動標記

一系列按鈕或連結圖示,每個圖示代表一個或多個專案,具體取決於輪播內每個滾動位置顯示的專案數量。

A carousel with a content area in the middle, previous and next buttons to the left and right, and scroll markers at the bottom

輪播的一個關鍵特性是分頁——專案感覺像是獨立的內容片段,使用者在它們之間移動,而不是形成一個連續的內容部分。你可以一次顯示一個專案,或者在每個輪播“頁面”上顯示多個專案。當多個專案可見時,每次按下“下一個”或“上一個”按鈕時,你可能會顯示一組全新的專案。或者,你可以在列表的一端新增一個新專案,同時將另一端的專案移出檢視。

使用 JavaScript 實現輪播可能相當脆弱且具有挑戰性。它需要指令碼將滾動標記與它們所代表的專案關聯起來,同時不斷更新滾動按鈕以保持其正常執行。當使用 JavaScript 建立輪播時,還必須額外新增輪播和相關控制元件的無障礙性。

幸運的是,我們可以使用 CSS 輪播特性建立帶有相關控制元件的無障礙輪播,而無需使用 JavaScript。

CSS 輪播特性提供了一些偽元素和偽類,使得僅使用 CSS 和 HTML 就可以建立輪播,瀏覽器以一種無障礙、靈活且一致的方式處理大部分滾動和連結引用。這些特性如下:

::scroll-button()

這些偽元素在滾動容器內部生成,代表滾動按鈕,用於將容器向指定方向滾動。

::scroll-marker-group

在滾動容器內部生成;用於收集和佈局滾動標記。

::scroll-marker

在滾動容器祖先元素的子元素內部或滾動容器的列內生成,以表示它們的滾動標記。可以選擇這些標記將容器滾動到其關聯的子元素或列,它們被收集在滾動容器的 ::scroll-marker-group 中以便進行佈局。

:target-current

這個偽類可以用來選擇當前啟用的滾動標記。它可以用來為當前啟用的標記提供高亮樣式,這對於可用性和無障礙性非常重要。

::column

當一個容器透過 CSS 多列布局 設定為多列顯示其內容時,此偽元素代表生成的各個列。它可以與 ::scroll-marker 結合使用,為每一列生成一個滾動標記。

我們的第一個示例是一個單頁輪播,每個專案佔據整個頁面。我們有滾動標記作為底部導航,以及頁面兩側的滾動按鈕,使使用者能夠移動到下一頁和上一頁。

我們將使用 flexbox 來佈局輪播,使用滾動捕捉來強制實現清晰的分頁,並使用錨點定位來定位滾動按鈕和滾動標記相對於輪播的位置。

HTML 由一個標題元素和一個無序列表組成,每個列表項都包含一些示例內容:

html
<h1>CSS carousel single item per page</h1>
<ul>
  <li>
    <h2>Page 1</h2>
  </li>
  <li>
    <h2>Page 2</h2>
  </li>
  <li>
    <h2>Page 3</h2>
  </li>
  <li>
    <h2>Page 4</h2>
  </li>
</ul>

我們使用 flexbox 來建立一行專案;<ul> 是 flex 容器,其子列表項 <li> 水平顯示,每個專案佔據輪播的整個寬度。

無序列表透過 100vwwidth 設定來填充視口的全寬;它還被賦予了 300pxheight 和一些 padding。然後我們使用 flexbox 來佈局列表——將 display 值設定為 flex,使得子列表項以行顯示(由於預設的 flex-direction 值為 row),每個專案之間有 4vwgap

css
ul {
  width: 100vw;
  height: 300px;
  padding: 20px;
  display: flex;
  gap: 4vw;
}

現在是時候為列表項設定樣式了。第一個宣告提供了基本的樣式。重要的宣告是 flex 值為 0 0 100%,這強制每個專案與容器(<ul>)一樣寬。結果,內容將溢位其容器,視口將水平滾動。

css
li {
  list-style-type: none;
  background-color: #eeeeee;
  border: 1px solid #dddddd;
  padding: 20px;

  flex: 0 0 100%;
}

li:nth-child(even) {
  background-color: cyan;
}

此外,每個偶數編號的列表項透過 :nth-child() 被賦予了不同的背景顏色,以便更容易地看到滾動效果。

在列表上設定滾動捕捉

在本節中,我們將在 <ul> 上設定一個 overflow 值,將其變成一個滾動容器,然後應用 CSS 滾動捕捉,使列表在內容滾動時捕捉到每個列表項的中心。

<ul> 上設定 overflow-x 值為 scroll,使其內容在列表內水平滾動,而不是整個視口滾動。然後使用 CSS 滾動捕捉來捕捉到每個“頁面”——設定 scroll-snap-type 值為 x mandatory,使列表成為一個滾動捕捉容器。關鍵字 x 會使容器的捕捉目標在水平方向上被捕捉,而關鍵字 mandatory 意味著容器在滾動動作結束時總是會捕捉到一個捕捉目標。

css
ul {
  overflow-x: scroll;
  scroll-snap-type: x mandatory;
}

接下來,在列表項上設定 scroll-snap-align 值為 center,這樣當列表滾動時,它會捕捉到每個列表項的中心。

css
li {
  scroll-snap-align: center;
}

到目前為止顯示的程式碼呈現如下:

嘗試透過滑動或使用捲軸來滾動列表,以檢視滾動捕捉的效果。無論你在哪裡結束滾動動作,一個專案總是會“捕捉”到位。

注意: 使用 CSS 輪播特性並非強制要求使用 CSS 滾動捕捉。但是,包含了滾動捕捉的輪播效果要好得多。如果沒有滾動捕捉,滾動按鈕和標記將不太可能在頁面之間乾淨地導航,結果會不盡如人意。

建立滾動按鈕

在本節中,我們將在示例中新增“上一個”和“下一個”滾動按鈕,以提供一個在輪播頁面之間導航的工具。這是透過使用 ::scroll-button() 偽元素實現的。

::scroll-button() 偽元素僅當其 content 屬性被設定為除 none 之外的值時,才會在滾動容器內部生成按鈕。每個 ::scroll-button() 代表一個滾動按鈕,其滾動方向由選擇器的引數指定。每個滾動容器最多可以生成四個滾動按鈕,每個按鈕將容器的內容向塊軸或行內軸的開始或結束方向滾動。

你還可以指定一個 * 引數來用樣式定位所有的 ::scroll-button() 偽元素。

首先,為所有滾動按鈕設定一些基本樣式,以及基於不同狀態的樣式。為鍵盤使用者設定 :focus 樣式是很重要的。此外,由於滾動按鈕在無法再向該方向滾動時會自動設定為 disabled,我們使用 :disabled 偽類來定位這種狀態。

css
ul::scroll-button(*) {
  border: 0;
  font-size: 2rem;
  background: none;
  color: black;
  opacity: 0.7;
  cursor: pointer;
}

ul::scroll-button(*):hover,
ul::scroll-button(*):focus {
  opacity: 1;
}

ul::scroll-button(*):active {
  translate: 1px 1px;
}

ul::scroll-button(*):disabled {
  opacity: 0.2;
  cursor: unset;
}

注意: 我們還為滾動按鈕設定了 cursor 值為 pointer,以使其更明顯地可以與之互動(這對通用使用者體驗認知無障礙性都是一種改進),並在滾動按鈕處於 :disabled 狀態時取消設定。

接下來,透過 content 屬性為左、右滾動按鈕設定適當的圖示,這也是導致滾動按鈕生成的原因:

css
ul::scroll-button(left) {
  content: "◄";
}

ul::scroll-button(right) {
  content: "►";
}

注意: 滾動按鈕會自動獲得一個適當的無障礙名稱,因此輔助技術會正確地播報它們。例如,上述按鈕具有一個隱式的 role,值為 button,它們的無障礙名稱分別是“向左滾動”和“向右滾動”。

定位滾動按鈕

我們已經建立了滾動按鈕。現在我們將使用 CSS 錨點定位將它們相對於輪播進行定位。

首先,在列表上設定一個參考 anchor-name。接下來,每個滾動按鈕的 position 設定為 absolute,其 position-anchor 屬性設定為在列表上定義的相同參考名稱,以將兩者關聯起來。

css
ul {
  anchor-name: --my-carousel;
}

ul::scroll-button(*) {
  position: absolute;
  position-anchor: --my-carousel;
}

為了實際定位每個滾動按鈕,我們在它們的內邊距屬性上設定值。我們使用 anchor() 函式來定位按鈕的指定邊相對於輪播的邊。在每種情況下,都使用 calc() 函式在按鈕邊緣和輪播邊緣之間新增一些空間。例如,左滾動按鈕的右邊緣被定位在輪播左邊緣向右 70 畫素的位置。

css
ul::scroll-button(left) {
  right: calc(anchor(left) - 70px);
  bottom: calc(anchor(top) + 13px);
}

ul::scroll-button(right) {
  left: calc(anchor(right) - 70px);
  bottom: calc(anchor(top) + 13px);
}

加入滾動按鈕的程式碼後,我們得到以下結果:

嘗試按下“上一個”和“下一個”滾動按鈕,看看頁面是如何滾動的,同時遵循滾動捕捉的行為。還要注意,當列表滾動到內容開始處時,“上一個”按鈕是如何自動停用的,而當列表滾動到內容結束處時,“下一個”按鈕是如何自動停用的。

建立滾動標記

滾動標記是一組按鈕,每個按鈕都將輪播滾動到與其中一個內容頁面相關的位置。它們提供了額外的導航工具,同時也指示了你在輪播頁面中的進度。

在本節中,我們將向輪播新增滾動標記,這涉及三個主要功能:

  • scroll-marker-group 屬性設定在滾動容器元素上。它需要被設定為一個非 none 的值,以便生成 ::scroll-marker-group 偽元素;它的值指定了滾動標記組在輪播的 Tab 順序和佈局盒順序中的出現位置(但不是 DOM 結構)——before 將其放在開始處,滾動按鈕之前,而 after 將其放在末尾。
  • ::scroll-marker-group 偽元素存在於滾動容器內部,用於將滾動標記作為一個整體進行收集、包含和佈局。
  • ::scroll-marker 偽元素存在於滾動容器祖先元素的子元素和 ::column 片段內部,並代表它們的滾動標記。這些標記被收集在祖先元素的 ::scroll-marker-group 中以便進行佈局。

首先,列表的 scroll-marker-group 屬性被設定為 after,這樣 ::scroll-marker-group 偽元素就會在焦點和佈局盒順序中被放置在列表的 DOM 內容之後;這意味著它位於滾動按鈕之後:

css
ul {
  scroll-marker-group: after;
}

注意:或者,可以使用 scroll-target-group 從一個包含一組 <a> 元素的現有元素建立一個滾動標記組容器。

接下來,列表的 ::scroll-marker-group 偽元素使用 CSS 錨點定位相對於輪播進行定位,類似於滾動按鈕,但它在輪播上水平居中,使用 justify-self 值為 anchor-center。該組使用 flexbox 進行佈局,justify-content 值為 centergap20px,這樣其子元素(::scroll-marker 偽元素)就會在 ::scroll-marker-group 內部居中,並且每個元素之間有間隙。

css
ul::scroll-marker-group {
  position: absolute;
  position-anchor: --my-carousel;
  top: calc(anchor(bottom) - 70px);
  justify-self: anchor-center;

  display: flex;
  justify-content: center;
  gap: 20px;
}

接下來,我們處理滾動標記本身的外觀和感覺;它們可以像任何其他生成內容一樣進行樣式設定。需要注意的是,我們需要為 content 屬性設定一個非 none 的值,以便實際生成滾動標記。我們還設定了一些基本樣式,使標記顯示為帶輪廓的圓形:

css
li::scroll-marker {
  content: "";
  width: 16px;
  height: 16px;
  background-color: transparent;
  border: 2px solid black;
  border-radius: 50%;
}

注意: 生成的內容預設是內聯的;我們可以對我們的滾動標記應用 widthheight,因為它們是作為 flex 專案進行佈局的。

最後,對於本節,使用 :target-current 偽類來選擇與當前可見“頁面”相對應的滾動標記,突出顯示使用者在內容中滾動的進度。在這種情況下,我們將 background-color 設定為 black,使其樣式為一個實心圓。

css
li::scroll-marker:target-current {
  background-color: black;
}

注意: 當使用 scroll-marker-group 屬性在滾動容器上建立滾動標記組容器時,滾動容器會以 tablist/tab 的語義進行渲染。你可以用鍵盤 Tab 到它,然後使用左、右(或上、下)游標鍵在不同的“頁面”之間移動,這也會按預期改變相關滾動標記和滾動按鈕的狀態。滾動標記也可以像預期的那樣正常地透過 Tab 鍵進行切換。

所有上述程式碼結合在一起,建立了以下結果:

自上一個即時示例以來,已經添加了滾動標記——嘗試按下它們直接跳轉到每個頁面。請注意當前標記是如何高亮的,這樣你就可以看到你在分頁中的位置。也請嘗試使用 Tab 鍵切換到滾動標記組,然後使用游標鍵迴圈瀏覽每個頁面。

你還可以透過向左和向右滑動、拖動捲軸或按下滾動按鈕來在頁面之間導航。

第二個示例是一個每頁有多個專案的輪播,它同樣包含了滾動按鈕滾動標記用於在頁面間導航。這個示例也是響應式的——根據視口的寬度,每個頁面上會出現不同數量的專案。

這個示例與單頁輪播示例非常相似,不同之處在於它不使用 flexbox 進行佈局,而是使用 CSS 多列布局::column 偽元素來建立跨越輪播整個寬度的任意列,這些列可能包含多個專案。

使用這種方法,我們可以確保如果視口變大或變小,而專案大小保持不變,我們永遠不會有部分專案顯示在滾動埠的邊緣之外。在這種情況下,滾動標記是按列在滾動容器片段上建立的,而不是按項在子元素上建立的。

HTML 與上一個示例非常相似,只是列表項的數量要多得多,而且由於一次會顯示多個專案,我們將其標記為專案而不是頁面:

html
...
  <li>
    <h2>Item 1</h2>
  </li>
...

這個示例的 CSS 也非常相似,除了在以下部分中解釋的規則外。

這個示例使用 CSS 多列布局,而不是 flexbox,來佈局輪播專案。columns 值為 1 強制每列都佔據容器的整個寬度,內容一次顯示一列。還應用了 text-align 值為 center,強制內容與列表中心對齊。

css
ul {
  width: 100vw;
  height: 300px;
  padding: 10px;

  overflow-x: scroll;
  scroll-snap-type: x mandatory;

  columns: 1;
  text-align: center;
}

我們為列表項提供了基本的盒子樣式,然後應用佈局樣式,以允許一個或多個專案適應單個內容列,具體取決於視口寬度。隨著列表變寬或變窄,數量會動態變化。

css
li {
  list-style-type: none;

  display: inline-block;
  height: 100%;
  width: 200px;

  background-color: #eeeeee;
  border: 1px solid #dddddd;
  padding: 20px;
  margin: 0 10px;

  text-align: left;
}

li:nth-child(even) {
  background-color: cyan;
}

關鍵的佈局屬性如下:

  • display 值為 inline-block 被設定為強制列表項並排排列,並使列表水平滾動。
  • 為它們設定了一個 200px 的絕對 width 來控制它們的大小,這意味著一個或多個專案將適應一個隨著視口寬度增長和收縮的列。
  • 為它們設定了 text-align 值為 left,以覆蓋父容器上設定的 text-align: center,因此專案內容將左對齊。

scroll-snap-align 屬性現在設定在 ::column 偽元素上——這些偽元素代表由 columns 屬性生成的內容列——而不是列表項。我們想要捕捉到每個完整的列,而不是每個單獨的列表項,每次滾動操作都顯示所有新專案。

css
ul::column {
  scroll-snap-align: center;
}

列滾動標記

在這個示例中,用於建立滾動標記的 CSS 與上一個示例幾乎相同,只是選擇器不同——滾動標記是在生成的 ::column 偽元素上建立的,而不是在列表項上。注意我們在這裡包含了兩個偽元素來在生成的列上生成滾動標記。

css
ul::column::scroll-marker {
  content: "";
  width: 16px;
  height: 16px;
  background-color: transparent;
  border: 2px solid black;
  border-radius: 10px;
}

ul::column::scroll-marker:target-current {
  background-color: black;
}

響應式輪播的渲染效果如下:

嘗試透過向左和向右滑動、使用捲軸、按下滾動按鈕以及按下滾動標記在不同頁面之間導航。功能與單頁 flexbox 示例類似,不同的是現在每個導航位置都有多個列表項;滾動標記設定在可能包含多個專案的列片段上,而不是每個專案上。

此外,嘗試調整螢幕寬度,你會看到列表內能容納的列表項數量發生了變化——因此生成的列數也發生了變化。隨著列數的變化,滾動標記的數量會動態更新,以便每個列都在滾動標記組中有所代表。

另見