建立複選元件

基礎概略說明如何建構可回應、自動調整且無障礙的多重選取元件,以提供排序和篩選使用者體驗。

在這篇文章中,我想分享如何建構多重選項元件的想法。歡迎前往示範操作。

示範

如果您喜歡看影片,請參考這篇文章的 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;
  }
}

這會跳過 <legend>,因為它只會指定 <div> 子項,以便調整空間。

螢幕截圖:顯示輸入項目之間的邊界間距,但不含圖例。

篩選器標籤和核取方塊

由於標籤文字是 <fieldset> 的直接子項,且在表單 30ch 的最大寬度範圍內,因此如果標籤文字過長,就可能會換行。文字會自動換行,但文字和核取方塊之間的對齊方式不正確。Flexbox 非常適合用於這類情況。

fieldset > div {
  display: flex;
  gap: 2ch;
  align-items: baseline;
}
螢幕截圖:顯示在多行設定的情況下,勾號如何對齊第一行文字。
請前往這個 Codepen 查看更多內容

動畫格線

版面配置動畫是由 Isotope 進行。效能強大且強大的外掛程式,適用於互動式排序和篩選。

JavaScript

除了協助安排整齊的動畫互動式方格,JavaScript 也用於修飾一些粗糙的邊緣。

將使用者輸入內容標準化

此設計包含一個表單,其中有兩種不同的輸入方式,且兩者不會序列化相同。不過,我們可以使用一些 JavaScript 將資料歸一

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

我選擇將 <select> 元素資料結構體對齊至分組核取方塊結構體。為此,請將 input 事件監聽器新增至 <select> 元素,並在該處對應 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 螢幕閱讀器宣布結果的螢幕截圖。

如今,我們將為所有使用者提供優異的輔助技術體驗,無論他們如何與輔助技術互動。

結論

既然你知道我如何做到,你會怎麼做呢? 🙂

讓我們多方嘗試,瞭解在網路上建構應用程式的所有方式。建立示範、張貼推文 連結,以便我們將其新增至下方的社群重混專區!

社群重混作品

尚無任何資料可提供!