Tworzenie komponentu wielokrotnego wyboru

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ą.

Wersja demonstracyjna

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.

Zrzut ekranu porównujący wersję jasną i ciemną na komputerze z paskiem bocznym z polami wyboru oraz wersję mobilną na iOS i Androidzie z elementem wielokrotnego wyboru.

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.

Podgląd zrzutu ekranu elementu wielokrotnego wyboru w Chrome na Androidzie, iPhonie i iPadzie. Na iPadzie i iPhonie opcja wielokrotnego wyboru jest włączona, a każde urządzenie zapewnia unikalne wrażenia zoptymalizowane pod kątem rozmiaru ekranu.

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><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>

Zrzut ekranu z nakładką informacyjną dotyczącą legendy i elementów fieldset, pokazujący kolor i nazwę elementu.

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><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>

Zrzut ekranu przedstawiający element wielokrotnego wyboru na komputerze.

Ś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.

Zrzut ekranu przedstawiający czytnik ekranu w systemie macOS odczytujący liczbę aktywnych filtró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:

Zrzut ekranu domyślnych stylów dla elementu fieldset i legendy.

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 elementumargin-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>.

Zrzut ekranu pokazujący odstępy między polami, ale bez legendy.

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;
}
Zrzut ekranu pokazujący, jak znacznik wyboru jest wyrównany do pierwszego wiersza tekstu w przypadku zawijania tekstu w wielu wierszach.
Więcej informacji znajdziesz w tym Codepenie

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.

Zrzut ekranu konsoli JavaScript w Narzędziach deweloperskich, na którym widać cel i znormalizowane wyniki danych.

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”.

Zrzut ekranu pokazujący czytnik ekranu w systemie macOS odczytujący wyniki.

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