建立複選元件

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

在這篇文章中,我想分享如何建構多重選項元件的想法。試用示範模式

示範

如果你偏好觀看影片,請參閱這篇文章的 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> 的預設樣式和版面配置是獨特的:

這張螢幕截圖顯示了 fieldset 和圖例的預設樣式。

通常,如要為子項元素留出間距,我會使用 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 螢幕閱讀器讀出結果的螢幕截圖。

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

結論

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

讓我們多方嘗試,瞭解在網路上建構應用程式的所有方式。請製作示範作品,並在推特上傳連結,我會將其加入下方的社群重混曲目錄!

社群重混作品

尚無任何資料可提供!