基礎概略說明如何建構可回應、自動調整且無障礙的多重選取元件,以提供排序和篩選使用者體驗。
在這篇文章中,我想分享如何建構多重選項元件的想法。試用示範模式。
如果你偏好觀看影片,請參閱這篇文章的 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;
}
}
這會跳過 <legend>
,因為它只會指定 <div>
子項,以便調整空間。
篩選器標籤和核取方塊
由於標籤文字是 <fieldset>
的直接子項,且在表單 30ch
的最大寬度內,因此如果標籤文字過長,就可能會換行。文字會自動換行,但文字和核取方塊之間的對齊方式不正確。Flexbox 非常適合用於這類情況。
fieldset > div {
display: flex;
gap: 2ch;
align-items: baseline;
}
動畫格線
版面配置動畫是由 Isotope 完成。這款外掛程式功能強大,可用於互動式排序和篩選。
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 個結果」的公告。
如今,我們將為所有使用者提供優異的輔助技術體驗,無論他們如何與輔助技術互動。
結論
既然你知道我如何做到,你會怎麼做呢? 🙂?
讓我們多方嘗試,瞭解在網路上建構應用程式的所有方式。請製作示範作品,並在推特上傳連結,我會將其加入下方的社群重混曲目錄!
社群重混作品
尚無任何資料可提供!