Podstawowe informacje o tym, jak tworzyć komponenty przycisków podzielonych w taki sposób, aby były dostępne dla wszystkich
W tym poście chcę podzielić się z Wami sposobem na tworzenie przycisków podzielonych . Wypróbuj wersję demonstracyjną
Jeśli wolisz film, oto wersja tego posta w YouTube:
Omówienie
Przyciski podzielone to przyciski, które zawierają przycisk główny i listę dodatkowych przycisków. Są one przydatne do wyświetlania często wykonywanych czynności, a także do umieszczania w nich mniej często używanych czynności, które są potrzebne tylko od czasu do czasu. Przycisk podzielony może być kluczowy, aby projekt o dużej ilości elementów nie sprawiał wrażenia przytłomionego. Zaawansowany przycisk może nawet zapamiętać ostatnie działanie użytkownika i przesunąć je do pozycji głównej.
W aplikacji poczty e-mail znajdziesz typowy przycisk dzielenia. Głównym działaniem jest wysłanie, ale możesz też wysłać później lub zapisać wersję roboczą:
Udostępnione pole działania jest przydatne, ponieważ użytkownik nie musi się rozglądać. Wiedzą, że najważniejsze czynności związane z e-mailami są dostępne na przycisku dzielenia.
Części
Zanim omówimy ogólną aranżację i wrażenia użytkownika, przyjrzyjmy się najważniejszym elementom przycisku podzielonego. Narzędzie VisBug do sprawdzania dostępności służy do wyświetlania widoku makro komponentu, w którym widoczne są aspekty kodu HTML, styl i dostępność poszczególnych głównych elementów.
Kontenery przycisku podziału na najwyższym poziomie
Element najwyższego poziomu to wbudowany flexbox z klasą gui-split-button
, który zawiera działanie główne i .gui-popup-button
.
Główny przycisk działania
Początkowo widoczny i możliwy do skoncentrowania element <button>
mieści się w kontenerze z 2 pasującymi do siebie kształtami narożników, aby interakcje fokusowania, najeżdżania kursorem i aktywności były widoczne w ramach elementu .gui-split-button
.
Przycisk przełączania okna
Element pomocy „wyskakujące okienko” służy do aktywowania i wskazania listy przycisków dodatkowych. Zwróć uwagę, że nie jest to <button>
i nie można go wybrać. Jest to jednak element kotwiczący dla .gui-popup
i host dla :focus-within
, które są używane do wyświetlania wyskakującego okienka.
Karta wyskakująca
To jest element potomny karty pływającej względem jej kotwicy .gui-popup-button
, umieszczony absolutnie i semantyczną otoczką listy przycisków.
dodatkowe działania
Przycisk <button>
, który można zaznaczyć, ma nieco mniejszy rozmiar czcionki niż główny przycisk. Zawiera ikonę i styl podobny do głównego przycisku.
Właściwości niestandardowe
Te zmienne pomagają tworzyć harmonię kolorów i stanowią centralne miejsce do modyfikowania wartości używanych w całym komponencie.
@custom-media --motionOK (prefers-reduced-motion: no-preference); @custom-media --dark (prefers-color-scheme: dark); @custom-media --light (prefers-color-scheme: light); .gui-split-button { --theme: hsl(220 75% 50%); --theme-hover: hsl(220 75% 45%); --theme-active: hsl(220 75% 40%); --theme-text: hsl(220 75% 25%); --theme-border: hsl(220 50% 75%); --ontheme: hsl(220 90% 98%); --popupbg: hsl(220 0% 100%); --border: 1px solid var(--theme-border); --radius: 6px; --in-speed: 50ms; --out-speed: 300ms; @media (--dark) { --theme: hsl(220 50% 60%); --theme-hover: hsl(220 50% 65%); --theme-active: hsl(220 75% 70%); --theme-text: hsl(220 10% 85%); --theme-border: hsl(220 20% 70%); --ontheme: hsl(220 90% 5%); --popupbg: hsl(220 10% 30%); } }
Układy i kolory
Znacznik
Element zaczyna się jako <div>
z niestandardową nazwą klasy.
<div class="gui-split-button"></div>
Dodaj przycisk główny i elementy .gui-popup-button
.
<div class="gui-split-button"> <button>Send</button> <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span> </div>
Zwróć uwagę na atrybuty ARIA aria-haspopup
i aria-expanded
. Te wskazówki są kluczowe dla czytników ekranu, aby wiedzieć, jakie funkcje są dostępne i jak działają przyciski. Atrybut title
jest przydatny dla wszystkich.
Dodaj ikonę <svg>
i element kontenera .gui-popup
.
<div class="gui-split-button"> <button>Send</button> <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"> <svg aria-hidden="true" viewBox="0 0 20 20"> <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" /> </svg> <ul class="gui-popup"></ul> </span> </div>
W przypadku prostego umieszczenia wyskakującego okienka element .gui-popup
jest elementem podrzędnym przycisku, który je otwiera. Jedynym haczykiem w przypadku tej strategii jest to, że kontener .gui-split-button
nie może używać overflow: hidden
, ponieważ spowoduje to przycięcie wyskakującego okienka.
<ul>
wypełniony treściami <li><button>
będzie dla czytników ekranu „listą przycisków”, ponieważ to właśnie ten interfejs jest wyświetlany.
<div class="gui-split-button"> <button>Send</button> <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"> <svg aria-hidden="true" viewBox="0 0 20 20"> <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" /> </svg> <ul class="gui-popup"> <li> <button>Schedule for later</button> </li> <li> <button>Delete</button> </li> <li> <button>Save draft</button> </li> </ul> </span> </div>
Aby nadać aplikacji charakter i trochę się zabawić kolorami, dodałem do przycisków dodatkowych ikony z witryny https://heroicons.com. Ikony są opcjonalne zarówno w przypadku przycisków głównych, jak i dodatkowych.
<div class="gui-split-button"> <button>Send</button> <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"> <svg aria-hidden="true" viewBox="0 0 20 20"> <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" /> </svg> <ul class="gui-popup"> <li><button> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /> </svg> Schedule for later </button></li> <li><button> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> </svg> Delete </button></li> <li><button> <svg aria-hidden="true" viewBox="0 0 24 24"> <path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" /> </svg> Save draft </button></li> </ul> </span> </div>
Style
Gdy kod HTML i treści są już gotowe, style mogą określić kolory i układ.
Nadawanie stylu kontenerowi podzielonego przycisku
W przypadku tego komponenta otaczającego dobrze sprawdza się typ wyświetlania inline-flex
, ponieważ powinien pasować do innych przycisków, działań lub elementów podzielonych.
.gui-split-button { display: inline-flex; border-radius: var(--radius); background: var(--theme); color: var(--ontheme); fill: var(--ontheme); touch-action: manipulation; user-select: none; -webkit-tap-highlight-color: transparent; }
Styl <button>
Przyciski bardzo dobrze ukrywają, ile kodu jest wymagane. Może być konieczne cofnięcie lub zastąpienie domyślnych stylów przeglądarki, ale trzeba też zastosować dziedziczenie, dodać stany interakcji i dostosować interfejs do różnych preferencji użytkownika i typów danych wejściowych. Style przycisków szybko się sumują.
Te przyciski różnią się od zwykłych przycisków, ponieważ mają ten sam kolor tła co element nadrzędny. Zazwyczaj przycisk ma swój kolor tła i tekstu. Te jednak udostępniają je i używają tylko własnego tła podczas interakcji.
.gui-split-button button { cursor: pointer; appearance: none; background: none; border: none; display: inline-flex; align-items: center; gap: 1ch; white-space: nowrap; font-family: inherit; font-size: inherit; font-weight: 500; padding-block: 1.25ch; padding-inline: 2.5ch; color: var(--ontheme); outline-color: var(--theme); outline-offset: -5px; }
Dodaj stany interakcji za pomocą kilku pseudoklas CSS i odpowiednich właściwości niestandardowych dla danego stanu:
.gui-split-button button { … &:is(:hover, :focus-visible) { background: var(--theme-hover); color: var(--ontheme); & > svg { stroke: currentColor; fill: none; } } &:active { background: var(--theme-active); } }
Aby uzyskać efekt wizualny, przycisk główny musi mieć kilka specjalnych stylów:
.gui-split-button > button { border-end-start-radius: var(--radius); border-start-start-radius: var(--radius); & > svg { fill: none; stroke: var(--ontheme); } }
Na koniec, aby dodać trochę uroku, przy jasnym motywie przycisk i ikona mają cień:
.gui-split-button { @media (--light) { & > button, & button:is(:focus-visible, :hover) { text-shadow: 0 1px 0 var(--theme-active); } & > .gui-popup-button > svg, & button:is(:focus-visible, :hover) > svg { filter: drop-shadow(0 1px 0 var(--theme-active)); } } }
Przycisk powinien być zaprojektowany z uwagą na mikrointerakcje i szczegóły.
Uwaga dotycząca :focus-visible
Zwróć uwagę, że style przycisków używają wartości :focus-visible
zamiast :focus
. :focus
to kluczowy element tworzenia dostępnego interfejsu użytkownika, ale ma jedną wadę: nie sprawdza, czy użytkownik musi zobaczyć dany element, czy nie.
W filmie poniżej staramy się rozłożyć na czynniki pierwsze tę mikrointerakcję, aby pokazać, że :focus-visible
to inteligentna alternatywa.
Stylowanie przycisku okna
4ch
Flexbox do wyśrodkowania ikony i zabezpieczenia listy przycisków w wyskakującym okienku. Podobnie jak przycisk główny, jest przezroczysty, dopóki nie najedziesz na niego kursorem lub nie wejdziesz z nim w interakcję, a potem rozszerza się, aby wypełnić całą powierzchnię.
.gui-popup-button { inline-size: 4ch; cursor: pointer; position: relative; display: inline-flex; align-items: center; justify-content: center; border-inline-start: var(--border); border-start-end-radius: var(--radius); border-end-end-radius: var(--radius); }
Warstwy w stanach najechania kursorem, najechania na element i aktywności za pomocą zagnieżdżania CSS i selektora funkcjonalnego :is()
:
.gui-popup-button { … &:is(:hover,:focus-within) { background: var(--theme-hover); } /* fixes iOS trying to be helpful */ &:focus { outline: none; } &:active { background: var(--theme-active); } }
Te style są głównym elementem umożliwiającym wyświetlanie i ukrywanie wyskakującego okienka. Jeśli element .gui-popup-button
ma element podrzędny focus
, ustaw pozycję opacity
i wartość pointer-events
dla ikony i wyskakującego okienka.
.gui-popup-button { … &:focus-within { & > svg { transition-duration: var(--in-speed); transform: rotateZ(.5turn); } & > .gui-popup { transition-duration: var(--in-speed); opacity: 1; transform: translateY(0); pointer-events: auto; } } }
Po skonfigurowaniu stylów wejścia i wyjścia należy warunkowo zastosować przekształcenia przejścia w zależności od preferencji użytkownika dotyczących animacji:
.gui-popup-button { … @media (--motionOK) { & > svg { transition: transform var(--out-speed) ease; } & > .gui-popup { transform: translateY(5px); transition: opacity var(--out-speed) ease, transform var(--out-speed) ease; } } }
Osoby, które dokładnie przyjrzą się kodom, zauważą, że przejścia w przezroczystości są nadal dostępne dla użytkowników, którzy wolą ograniczone ruchy.
Stylizacja wyskakującego okienka
Element .gui-popup
to lista przycisków karty, która jest wyświetlana w układce, a jej właściwości niestandardowe i jednostki względne są nieco mniejsze, aby pasowały do głównego przycisku, a także do marki pod względem koloru. Zwróć uwagę, że ikony mają mniejszy kontrast, są cieńsze, a cień ma odcień niebieskiego charakterystyczny dla marki. Podobnie jak w przypadku przycisków, dobry interfejs użytkownika i dobre wrażenia użytkownika to efekt tych drobnych szczegółów.
.gui-popup { --shadow: 220 70% 15%; --shadow-strength: 1%; opacity: 0; pointer-events: none; position: absolute; bottom: 80%; left: -1.5ch; list-style-type: none; background: var(--popupbg); color: var(--theme-text); padding-inline: 0; padding-block: .5ch; border-radius: var(--radius); overflow: hidden; display: flex; flex-direction: column; font-size: .9em; transition: opacity var(--out-speed) ease; box-shadow: 0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)), 0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)), 0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)), 0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)), 0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)), 0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%)) ; }
Ikony i przyciski mają kolory marki, aby dobrze wyglądały na kartach z ciemnym i jasnym motywem:
.gui-popup { … & svg { fill: var(--popupbg); stroke: var(--theme); @media (prefers-color-scheme: dark) { stroke: var(--theme-border); } } & button { color: var(--theme-text); width: 100%; } }
Wyskakujące okienko w ciemnym motywie zawiera dodatkowy cień tekstu i ikony oraz nieco bardziej intensywny cień pola:
.gui-popup { … @media (--dark) { --shadow-strength: 5%; --shadow: 220 3% 2%; & button:not(:focus-visible, :hover) { text-shadow: 0 1px 0 var(--ontheme); } & button:not(:focus-visible, :hover) > svg { filter: drop-shadow(0 1px 0 var(--ontheme)); } } }
Ogólne style ikon <svg>
Wszystkie ikony są dopasowane rozmiarem do przycisku font-size
, w którym są używane, przy użyciu jednostki ch
jako inline-size
. Każdy z nich ma też kilka stylów, które nadają ikonom miękkość i gładkość.
.gui-split-button svg { inline-size: 2ch; box-sizing: content-box; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2px; }
Układ od prawej do lewej
Właściwości logiczne wykonują całą skomplikowaną pracę. Oto lista użytych właściwości logicznych: - display: inline-flex
tworzy element flex w wierszu. – padding-block
i padding-inline
jako parę zamiast padding
skrót, aby uzyskać korzyści z wypełniania stron logicznych. – border-end-start-radius
i znajomi będą mieli zaokrąglone rogi w zależności od kierunku dokumentu. – inline-size
zamiast width
zapewnia, że rozmiar nie jest powiązany z wymiarami fizycznymi. – border-inline-start
dodaje na początku obramowanie, które może znajdować się po prawej lub lewej stronie w zależności od kierunku skryptu.
JavaScript
Prawie cały wymieniony poniżej kod JavaScript służy do ułatwiania dostępu. Aby ułatwić wykonywanie zadań, używam 2 bibliotek pomocniczych. Biblioteka BlingBlingJS służy do krótkich zapytań DOM i łatwego konfigurowania odbiornika zdarzeń, a biblioteka roving-ux ułatwia obsługę klawiatury i kontrolera w przypadku wyskakujących okienek.
import $ from 'blingblingjs' import {rovingIndex} from 'roving-ux' const splitButtons = $('.gui-split-button') const popupButtons = $('.gui-popup-button')
Po zaimportowaniu powyższych bibliotek i wybraniu elementów oraz zapisaniu ich w zmiennych wystarczy kilka funkcji, aby uaktualnić tę funkcję.
indeks przemieszczania się,
Gdy klawiatura lub czytnik ekranu zaznaczy .gui-popup-button
, chcemy przekazać zaznaczenie pierwszemu (lub ostatnio zaznaczonemu) przyciskowi w .gui-popup
. Biblioteka pomaga nam w tym za pomocą parametrów element
i target
.
popupButtons.forEach(element => rovingIndex({ element, target: 'button', }))
Element przekazuje teraz fokus do elementów podrzędnych <button>
i umożliwia standardową nawigację za pomocą klawiszy strzałek, aby przeglądać opcje.
Przełączanie aria-expanded
Chociaż wizualnie widać, że wyskakujące okienko się wyświetla i chowa, czytnik ekranu potrzebuje więcej niż tylko wskazówek wizualnych. Język JavaScript jest tu używany do uzupełnienia interakcji :focus-within
sterowanej przez arkusz CSS poprzez przełączanie atrybutu odpowiedniego dla czytnika ekranu.
popupButtons.on('focusin', e => { e.currentTarget.setAttribute('aria-expanded', true) }) popupButtons.on('focusout', e => { e.currentTarget.setAttribute('aria-expanded', false) })
Włączanie klucza Escape
Użytkownik celowo został skierowany do pułapki, więc musimy zapewnić mu możliwość wyjścia z niej. Najczęstszym sposobem jest zezwolenie na użycie klucza Escape
. Aby to zrobić, obserwuj naciśnięcia klawiszy na przycisku wyskakującego okienka, ponieważ wszystkie zdarzenia klawiatury w elementach potomnych będą przekazywane do tego elementu nadrzędnego.
popupButtons.on('keyup', e => { if (e.code === 'Escape') e.target.blur() })
Jeśli przycisk wyskakującego okienka wykryje naciśnięcie klawisza Escape
, przestaje być aktywny.blur()
Kliknięcia przycisku podziału
Jeśli użytkownik klika, stuka lub używa klawiatury, aplikacja musi wykonać odpowiednie działanie. Tutaj ponownie używamy przenoszenia zdarzeń, ale tym razem w kontekście kontenera .gui-split-button
, aby rejestrować kliknięcia przycisku z wyskakujących okienek podrzędnych lub z głównego działania.
splitButtons.on('click', event => { if (event.target.nodeName !== 'BUTTON') return console.info(event.target.innerText) })
Podsumowanie
Teraz, gdy już wiesz, jak to zrobić, jak Ty to zrobisz? 🙂
Zróżnicujemy nasze podejścia i poznamy wszystkie sposoby tworzenia stron internetowych. Utwórz wersję demonstracyjną, wyślij mi linki, a ja dodam je do sekcji z remiksami społeczności.
Remiksy społeczności
- Codepen autorstwa Joost van der Schee