基礎總覽:如何建構自動調整式無障礙多重選取元件,提供排序和篩選使用者體驗。
在這篇文章中,我想分享如何建構多選元件的思考過程。試用示範模式。
如果比較喜歡看影片,可以觀看這篇貼文的 YouTube 版本:
總覽
使用者通常會看到許多項目 (有時是大量項目),在這種情況下,建議提供縮減清單的方式,避免選擇過多。這篇網誌文章探討如何透過篩選器 UI 減少選項。這項功能會顯示使用者可選取或取消選取的商品屬性,藉此縮小搜尋結果範圍,減少選擇過多的情況。
互動
目標是讓所有使用者都能快速瀏覽篩選器選項,並支援各種輸入類型。這會透過一組可調整大小的回應式元件提供。傳統的核取方塊側欄,適用於電腦、鍵盤和螢幕閱讀器,以及適用於觸控使用者的 <select
multiple>
。
決定在觸控裝置上使用內建多選功能,而非在桌機上使用,可節省工作時間並創造工作,但相信與在單一元件中建構整個回應式體驗相比,這樣做能提供適當的體驗,且程式碼債務較少。
觸控
觸控元件可節省空間,並提升行動裝置上的使用者互動準確度。這項功能會將整個核取方塊側欄摺疊成內建的疊加觸控體驗,藉此節省空間。<select>
系統會提供大型觸控疊加層體驗,協助提高輸入準確度。
鍵盤和遊戲手把
以下示範如何使用鍵盤上的 <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 執行這項工作,因為這超出計數器可執行的範圍。
期待寶寶的到來
使用 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;
}

動畫格線
版面配置動畫是由 Isotope 製作,這個外掛程式效能優異且功能強大,可提供互動式排序和篩選功能。
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 個結果」的公告。
現在,無論使用者如何與裝置互動,都能享有優質的輔助技術體驗。
結論
現在您已瞭解我的做法,您會怎麼做呢?🙂
讓我們多元化方法,學習在網路上建構內容的所有方式。 建立試聽版,然後在推特上傳送連結給我,我會將連結加到下方的社群混音區!
社群重混作品
目前沒有任何資料可提供!