Podstawowe informacje o tym, jak utworzyć responsywny, adaptacyjny i dostępny komponent wielokrotnego wyboru, który będzie przydatny w przypadku sortowania i filtrowania.
W tym poście chcę podzielić się przemyśleniami na temat sposobu tworzenia komponentu wielokrotnego wyboru. Wypróbuj wersję demonstracyjną.
Jeśli wolisz film, obejrzyj tę wersję posta w YouTube:
Przegląd
Użytkownicy często widzą wiele produktów, a w takich przypadkach warto udostępnić sposób na skrócenie listy, aby zapobiec przeciążeniu informacjami. W tym poście na blogu omawiamy interfejs filtrowania jako sposób na ograniczenie liczby opcji. Działa to w ten sposób, że wyświetla atrybuty produktów, które użytkownicy mogą zaznaczać lub odznaczać, co zmniejsza liczbę wyników, a tym samym ogranicza nadmiar opcji.
Interakcje
Chcemy umożliwić wszystkim użytkownikom szybkie przeglądanie opcji filtrowania i różnych typów danych wejściowych. Będzie ona dostarczana z dopasowującą się i elastyczną parą komponentów. Tradycyjny pasek boczny z polami wyboru dla użytkowników komputerów, klawiatur i czytników ekranu oraz <select
multiple>
dla użytkowników urządzeń dotykowych.
Decyzja o użyciu wbudowanego wielokrotnego wyboru w przypadku urządzeń dotykowych, a nie komputerów, pozwala zaoszczędzić pracę i ją tworzy, ale uważam, że zapewnia odpowiednie wrażenia przy mniejszym zadłużeniu kodu niż tworzenie całego środowiska responsywnego w jednym komponencie.
Dotyk
Komponent dotykowy oszczędza miejsce i zwiększa dokładność interakcji użytkownika na urządzeniach mobilnych. Pozwala to zaoszczędzić miejsce, ponieważ cała lista pól wyboru jest zwinięta do <select>
wbudowanej nakładki dotykowej. Zwiększa dokładność wprowadzania danych, wyświetlając dużą nakładkę dotykową udostępnianą przez system.
Klawiatura i pad do gier
Poniżej znajdziesz demonstrację użycia <select multiple>
na klawiaturze.
Wbudowany wielokrotny wybór nie może być stylizowany i jest dostępny tylko w kompaktowym układzie, który nie nadaje się do prezentowania wielu opcji. Widzisz, że w tym małym polu nie widać wszystkich opcji? Możesz zmienić jego rozmiar, ale nadal nie jest tak wygodny w użyciu jak pasek boczny z polami wyboru.
Znacznik
Oba komponenty będą znajdować się w tym samym elemencie <form>
. Wyniki tego formularza, niezależnie od tego, czy są to pola wyboru czy wielokrotny wybór, będą obserwowane i używane do filtrowania siatki, ale mogą też być przesyłane na serwer.
<form>
</form>
Komponent pól wyboru
Grupy pól wyboru powinny być umieszczone w elemencie
<fieldset>
i mieć atrybut
<legend>
.
Gdy kod HTML jest tak skonstruowany, czytniki ekranu i interfejs FormData automatycznie rozpoznają relacje między elementami.
<form>
<fieldset>
<legend>New</legend>
… checkboxes …
</fieldset>
</form>
Po utworzeniu grup dodaj <label>
i <input type="checkbox">
dla każdego filtra. Ja umieściłem je w tagu <div>
, aby właściwość CSS gap
mogła równomiernie rozmieścić elementy i zachować wyrównanie, gdy etykiety będą wielowierszowe.
<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>
Komponent <select multiple>
Rzadko używaną funkcją elementu <select>
jest multiple
.
Gdy atrybut jest używany z elementem <select>
, użytkownik może wybrać wiele elementów z listy. To tak, jakby zmienić interakcję z listy opcji na listę pól wyboru.
<form>
<select multiple="true" title="Filter results by category">
…
</select>
</form>
Aby oznaczyć i utworzyć grupy w <select>
, użyj elementu <optgroup>
i nadaj mu atrybut label
oraz wartość. Ten element i wartość atrybutu są podobne do elementów <fieldset>
i <legend>
.
<form>
<select multiple="true" title="Filter results by category">
<optgroup label="New">
…
</optgroup>
</select>
</form>
Teraz dodaj elementy
<option>
do filtra.
<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>
Śledzenie danych wejściowych za pomocą liczników w celu informowania technologii wspomagających
W tym przypadku używana jest technika status
role
do śledzenia i utrzymywania liczby filtrów dla czytników ekranu i innych technologii wspomagających. Film w YouTube
prezentuje tę funkcję. Integracja zaczyna się od kodu HTML i atrybutu role="status"
.
<div role="status" class="sr-only" id="applied-filters"></div>
Ten element będzie odczytywać na głos zmiany wprowadzone w treści. Możemy aktualizować zawartość za pomocą liczników CSS, gdy użytkownicy wchodzą w interakcję z polami wyboru. Aby to zrobić, musimy najpierw utworzyć licznik o nazwie w elemencie nadrzędnym elementów wejściowych i elementu stanu.
aside {
counter-reset: filters;
}
Domyślnie liczba będzie wynosić 0
, co jest świetne, ponieważ w tym projekcie domyślnie nic nie jest :checked
.
Następnie, aby zwiększyć wartość nowo utworzonego licznika, wybierzemy elementy podrzędne elementu
<aside>
, które są elementami :checked
. Gdy użytkownik zmienia stan danych wejściowych, licznik filters
zwiększa się.
aside :checked {
counter-increment: filters;
}
CSS rozpoznaje teraz ogólną liczbę elementów interfejsu pola wyboru, a element roli stanu jest pusty i oczekuje na wartości. CSS przechowuje sumę w pamięci, więc funkcja counter()
umożliwia dostęp do wartości z zawartości pseudoelementu:
aside #applied-filters::before {
content: counter(filters) " filters ";
}
Element roli stanu w kodzie HTML będzie teraz odczytywany przez czytnik ekranu jako „2 filtry”. To dobry początek, ale możemy zrobić więcej, np. udostępnić liczbę wyników, które zostały zaktualizowane przez filtry. Zrobimy to w JavaScript, ponieważ wykracza to poza możliwości liczników.
Ekscytacja związana z przygotowaniami
Algorytm liczników świetnie się sprawdził w przypadku CSS nesting-1, ponieważ całą logikę udało mi się umieścić w jednym bloku. Jest przenośny i scentralizowany, co ułatwia odczytywanie i aktualizowanie.
aside {
counter-reset: filters;
& :checked {
counter-increment: filters;
}
& #applied-filters::before {
content: counter(filters) " filters ";
}
}
Układy
W tej sekcji opisujemy układy między tymi 2 komponentami. Większość stylów układu jest przeznaczona dla komponentu pola wyboru na komputerze.
formularz,
Aby zoptymalizować czytelność i łatwość przeglądania dla użytkowników, formularz ma maksymalną szerokość 30 znaków, co w zasadzie wyznacza optyczną szerokość wiersza dla każdej etykiety filtra. Formularz korzysta z układu siatki i właściwości gap
, aby rozmieścić elementy fieldset.
form {
display: grid;
gap: 2ch;
max-inline-size: 30ch;
}
Element <select>
Lista etykiet i pól wyboru zajmuje zbyt dużo miejsca na urządzeniach mobilnych. Dlatego układ sprawdza, czy użytkownik korzysta z głównego urządzenia wskazującego, aby dostosować działanie do obsługi dotykowej.
@media (pointer: coarse) {
select[multiple] {
display: block;
}
}
Wartość coarse
oznacza, że użytkownik nie będzie mógł precyzyjnie korzystać z ekranu za pomocą głównego urządzenia wejściowego. Na urządzeniu mobilnym wartość wskaźnika to często coarse
, ponieważ główną metodą interakcji jest dotyk. Na komputerze wartość wskaźnika to często fine
, ponieważ zwykle jest do niego podłączona mysz lub inne urządzenie wejściowe o wysokiej precyzji.
Zestawy pól
Domyślny styl i układ <fieldset>
z <legend>
jest niepowtarzalny:
Zwykle do rozmieszczenia elementów podrzędnych używam właściwości gap
, ale unikalne położenie elementu <legend>
utrudnia utworzenie równomiernie rozmieszczonego zestawu elementów podrzędnych. Zamiast gap
używane są selektor sąsiedniego elementu i margin-block-start
.
fieldset {
padding: 2ch;
& > div + div {
margin-block-start: 2ch;
}
}
Dzięki temu element <legend>
nie będzie dostosowywał swojego miejsca, ponieważ kierowanie jest ograniczone tylko do elementów podrzędnych <div>
.
Etykieta filtra i pole wyboru
Jako bezpośredni element podrzędny tagu <fieldset>
i w ramach maksymalnej szerokości formularza 30ch
tekst etykiety może się zawijać, jeśli jest zbyt długi. Zawijanie tekstu jest przydatne, ale
nieprawidłowe wyrównanie tekstu i pola wyboru już nie. Układ flexbox idealnie się do tego nadaje.
fieldset > div {
display: flex;
gap: 2ch;
align-items: baseline;
}

Animowana siatka
Animacja układu jest wykonywana przez Isotope. Wydajna i zaawansowana wtyczka do interaktywnego sortowania i filtrowania.
JavaScript
Oprócz pomocy w tworzeniu animowanej, interaktywnej siatki JavaScript jest używany do dopracowania kilku niedoskonałości.
Normalizowanie danych wejściowych użytkownika
Ten projekt ma 1 formularz z 2 różnymi sposobami wprowadzania danych, które nie są serializowane w ten sam sposób. Za pomocą kodu JavaScript możemy jednak znormalizować dane.
Zdecydowałem się dopasować strukturę danych elementu <select>
do struktury zgrupowanych pól wyboru. W tym celu do elementu <select>
dodawany jest detektor zdarzeń input
, a następnie mapowane są elementy 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
}, [])
})
Teraz możesz przesłać formularz lub, w przypadku tej wersji demonstracyjnej, poinstruować Isotope, według czego ma filtrować.
Kończenie elementu roli stanu
Element zlicza i ogłasza liczbę filtrów tylko na podstawie interakcji z polem wyboru, ale uznałem, że warto dodatkowo podać liczbę wyników i upewnić się, że uwzględniane są też wybory elementów <select>
.
Wybrany element <select>
jest odzwierciedlony w counter()
.
W sekcji normalizacji danych na wejściu utworzono już odbiornik. Na końcu tej funkcji znana jest liczba wybranych filtrów i liczba wyników dla tych filtrów. Wartości można przekazywać do elementu roli stanu w ten sposób:
let statusRoleElement = document.querySelector('#applied-filters')
statusRoleElement.style.counterSet = selectData.length
Wyniki odzwierciedlone w elemencie role="status"
:checked
zapewnia wbudowany sposób przekazywania liczby wybranych filtrów do elementu roli stanu, ale nie wyświetla liczby odfiltrowanych wyników.
JavaScript może śledzić interakcje z polami wyboru i po odfiltrowaniu siatki dodawać textContent
, tak jak robi to element <select>
.
document
.querySelector('aside form')
.addEventListener('input', e => {
// isotope demo code
let filterResults = IsotopeGrid.getFilteredItemElements().length
document.querySelector('#applied-filters').textContent = `giving ${filterResults} results`
})
W ten sposób uzupełniamy komunikat „2 filtry dające 25 wyników”.
Teraz nasze doskonałe technologie wspomagające będą dostępne dla wszystkich użytkowników, niezależnie od tego, w jaki sposób z nich korzystają.
Podsumowanie
Teraz, gdy wiesz, jak to zrobiłem, jak Ty byś to zrobił? 🙂
Urozmaićmy nasze podejście i poznajmy wszystkie sposoby tworzenia treści w internecie. Utwórz demo, wyślij mi na Twitterze linki, a ja dodam je do sekcji remiksów społeczności poniżej.
Remiksy społeczności
Na razie jest tu pusto