建立複選元件

基礎總覽:如何建構自動調整式無障礙多重選取元件,提供排序和篩選使用者體驗。

在這篇文章中,我想分享如何建構多選元件的思考過程。試用示範模式

示範

如果比較喜歡看影片,可以觀看這篇貼文的 YouTube 版本:

總覽

使用者通常會看到許多項目 (有時是大量項目),在這種情況下,建議提供縮減清單的方式,避免選擇過多。這篇網誌文章探討如何透過篩選器 UI 減少選項。這項功能會顯示使用者可選取或取消選取的商品屬性,藉此縮小搜尋結果範圍,減少選擇過多的情況。

互動

目標是讓所有使用者都能快速瀏覽篩選器選項,並支援各種輸入類型。這會透過一組可調整大小的回應式元件提供。傳統的核取方塊側欄,適用於電腦、鍵盤和螢幕閱讀器,以及適用於觸控使用者的 <select multiple>

比較螢幕截圖:顯示電腦版淺色和深色模式,以及含有核取方塊的側邊欄;iOS 和 Android 行動版則顯示多重選取元素。

決定在觸控裝置上使用內建多選功能,而非在桌機上使用,可節省工作時間並創造工作,但相信與在單一元件中建構整個回應式體驗相比,這樣做能提供適當的體驗,且程式碼債務較少。

觸控

觸控元件可節省空間,並提升行動裝置上的使用者互動準確度。這項功能會將整個核取方塊側欄摺疊成內建的疊加觸控體驗,藉此節省空間。<select>系統會提供大型觸控疊加層體驗,協助提高輸入準確度。

Android、iPhone 和 iPad 上的 Chrome 多選元素螢幕截圖預覽畫面。iPad 和 iPhone 的多選切換按鈕會開啟,且兩者都可獲得針對螢幕大小最佳化的專屬體驗。

鍵盤和遊戲手把

以下示範如何使用鍵盤上的 <select multiple>

這個內建多重選取畫面無法設定樣式,且僅提供不適合呈現大量選項的精簡版面配置。您會發現,在這麼小的方塊中,根本無法看到所有選項。雖然可以變更大小,但仍不如核取方塊側欄實用。

標記

這兩個元件會包含在同一個 <form> 元素中。系統會觀察並使用這類表單的結果 (無論是核取方塊或多選),篩選格線,但結果也可能會提交至伺服器。

<form>

</form>

核取方塊元件

核取方塊群組應包裝在 <fieldset> 元素中,並提供 <legend>。如果 HTML 結構符合上述條件,螢幕閱讀器和 FormData 就會自動瞭解元素之間的關係。

<form>
  <fieldset>
    <legend>New</legend>
    … checkboxes …
  </fieldset>
</form>

完成分組後,請為每個篩選器新增 <label><input type="checkbox">。我選擇將其包裝在 <div> 中,這樣 CSS gap 屬性就能平均間隔,並在標籤換行時維持對齊。

<form>
  <fieldset>
    <legend>New</legend>
    <div>
      <input type="checkbox" id="last 30 days" name="new" value="last 30 days">
      <label for="last 30 days">Last 30 Days</label>
    </div>
    <div>
      <input type="checkbox" id="last 6 months" name="new" value="last 6 months">
      <label for="last 6 months">Last 6 Months</label>
    </div>
   </fieldset>
</form>

螢幕截圖:圖例和欄位集元素的資訊疊加層,顯示顏色和元素名稱。

<select multiple> 元件

<select> 元素很少使用的功能是 multiple。如果屬性與 <select> 元素搭配使用,使用者可以從清單中選擇多個項目。這就像是將互動從單選清單變更為核取方塊清單。

<form>
  <select multiple="true" title="Filter results by category">
    …
  </select>
</form>

如要在 <select> 內標記及建立群組,請使用 <optgroup> 元素,並為該元素提供 label 屬性和值。這個元素和屬性值類似於 <fieldset><legend> 元素。

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      …
    </optgroup>
  </select>
</form>

現在新增篩選器的 <option> 元素。

<form>
  <select multiple="true" title="Filter results by category">
    <optgroup label="New">
      <option value="last 30 days">Last 30 Days</option>
      <option value="last 6 months">Last 6 Months</option>
    </optgroup>
  </select>
</form>

螢幕截圖:在桌機上轉譯多選元素。

使用計數器追蹤輸入內容,為輔助技術提供資訊

使用者體驗會採用「狀態」角色技術,追蹤及維護螢幕閱讀器和其他輔助技術的篩選器數量。YouTube 影片 展示這項功能。整合作業會從 HTML 和 role="status" 屬性開始。

<div role="status" class="sr-only" id="applied-filters"></div>

這個元素會朗讀內容的變更。使用者與核取方塊互動時,我們可以透過 CSS 計數器更新內容。為此,我們首先需要在輸入和狀態元素的父項元素上,建立具有名稱的計數器。

aside {
  counter-reset: filters;
}

根據預設,計數會是 0,這很棒,因為這個設計中預設沒有任何 :checked

接著,如要遞增新建立的計數器,請以 <aside> 元素的子項為目標,這些子項是 :checked。使用者變更輸入內容的狀態時,filters 計數器會累計。

aside :checked {
  counter-increment: filters;
}

CSS 現在會知道核取方塊 UI 的一般計數,且狀態角色元素為空白,等待值。由於 CSS 會在記憶體中維護計數,因此 counter() 函式可從虛擬元素內容存取值:

aside #applied-filters::before {
  content: counter(filters) " filters ";
}

狀態角色元素的 HTML 現在會向螢幕閱讀器播報「2 個篩選條件」。這是個好的開始,但我們可以做得更好,例如分享篩選器更新的結果總數。我們會從 JavaScript 執行這項工作,因為這超出計數器可執行的範圍。

螢幕截圖:MacOS 螢幕閱讀器會朗讀有效篩選條件的數量。

期待寶寶的到來

使用 CSS 巢狀結構 - 1 時,計數器演算法感覺很棒,因為我能夠將所有邏輯放入一個區塊。方便攜帶,且集中管理,方便閱讀和更新。

aside {
  counter-reset: filters;

  & :checked {
    counter-increment: filters;
  }

  & #applied-filters::before {
    content: counter(filters) " filters ";
  }
}

版面配置

本節說明這兩個元件之間的版面配置。大多數版面配置樣式適用於電腦版核取方塊元件。

表單

為方便使用者閱讀及瀏覽,表單的寬度上限為 30 個字元,也就是為每個篩選器標籤設定光學行寬。表單會使用格線版面配置和 gap 屬性,將 fieldset 間隔開來。

form {
  display: grid;
  gap: 2ch;
  max-inline-size: 30ch;
}

<select> 元素

標籤和核取方塊清單在行動裝置上佔用太多空間。 因此,版面配置會檢查使用者的主要指標裝置,以變更觸控體驗。

@media (pointer: coarse) {
  select[multiple] {
    display: block;
  }
}

coarse 值表示使用者無法透過主要輸入裝置,與螢幕進行高精確度的互動。在行動裝置上,指標值通常為 coarse,因為主要互動方式是觸控。在電腦裝置上,指標值通常為 fine,因為電腦通常會連線滑鼠或其他高精確度輸入裝置。

欄位集

<fieldset> 的預設樣式和版面配置與 <legend> 不同:

螢幕截圖:欄位集和圖例的預設樣式。

一般來說,如要間隔子項元素,我會使用 gap 屬性,但 <legend> 的獨特定位方式,使得難以建立間隔均勻的子項集合。改用相鄰同層級選取器margin-block-start,而非 gap

fieldset {
  padding: 2ch;

  & > div + div {
    margin-block-start: 2ch;
  }
}

這樣一來,只要指定 <div> 子項,即可略過 <legend> 的空間調整作業。

螢幕截圖:顯示輸入內容之間的邊界間距,但圖例除外。

篩選器標籤和核取方塊

做為 <fieldset> 的直接子項,且在表單 30ch 的最大寬度內,如果標籤文字過長,可能會換行。文字換行很棒,但文字和核取方塊之間不對齊就不是了。Flexbox 非常適合用於此用途。

fieldset > div {
  display: flex;
  gap: 2ch;
  align-items: baseline;
}
螢幕截圖:顯示在多行換行情境中,核取記號如何與第一行文字對齊。
Codepen 中播放更多內容

動畫格線

版面配置動畫是由 Isotope 製作,這個外掛程式效能優異且功能強大,可提供互動式排序和篩選功能。

JavaScript

除了協助編排整齊的動畫互動格線,JavaScript 也用於修飾幾個粗糙的邊緣。

正規化使用者輸入內容

這個設計有一個表單,但提供輸入的方式有兩種,而且這兩種方式不會序列化相同的內容。不過,我們可以透過一些 JavaScript 正規化資料。

開發人員工具 JavaScript 控制台的螢幕截圖,顯示目標和正規化資料結果。

我選擇將 <select> 元素資料結構與分組的核取方塊結構對齊。為此,系統會在 <select> 元素中新增 input 事件監聽器,此時會對應 selectedOptions

document.querySelector('select').addEventListener('input', event => {
  // make selectedOptions iterable then reduce a new array object
  let selectData = Array.from(event.target.selectedOptions).reduce((data, opt) => {
    // parent optgroup label and option value are added to the reduce aggregator
    data.push([opt.parentElement.label.toLowerCase(), opt.value])
    return data
  }, [])
})

現在可以安全地提交表單,或在本示範中指示 Isotope 要依據哪些項目篩選。

完成狀態角色元素

這個元素只會根據核取方塊互動來計算和公布篩選器數量,但我覺得額外分享結果數量,並確保 <select> 元素選項也計入,是個好主意。

<select> 元素選擇反映在 counter()

在資料正規化部分,系統已在輸入內容上建立接聽程式。函式結尾會顯示所選篩選條件的數量,以及符合這些篩選條件的結果數量。值可以傳遞至狀態角色元素,如下所示。

let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length

結果會反映在 role="status" 元素中

:checked 提供內建方式,可將所選篩選器的數量傳遞至狀態角色元素,但無法顯示篩選後的結果數量。JavaScript 可以監控與核取方塊的互動,並在篩選格線後新增 textContent,就像 <select> 元素一樣。

document
  .querySelector('aside form')
  .addEventListener('input', e => {
    // isotope demo code
    let filterResults = IsotopeGrid.getFilteredItemElements().length
    document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})

這項工作完成後,就會發布「2 個篩選條件,25 個結果」的公告。

螢幕截圖:macOS 螢幕閱讀器朗讀結果。

現在,無論使用者如何與裝置互動,都能享有優質的輔助技術體驗。

結論

現在您已瞭解我的做法,您會怎麼做呢?🙂

讓我們多元化方法,學習在網路上建構內容的所有方式。 建立試聽版,然後在推特上傳送連結給我,我會將連結加到下方的社群混音區!

社群重混作品

目前沒有任何資料可提供!