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 rzadziej używanych czynności, które mogą być ukryte do czasu, aż będą potrzebne. 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ólne skoordynowanie i wrażenia końcowego użytkownika, przyjrzyjmy się najważniejszym elementom przycisku podzielonego. W tym celu używamy narzędzia do sprawdzania dostępności VisBug, które pozwala wyświetlić widok makro komponentu, pokazując 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 skupienia <button>
mieści się w kontenerze z 2 pasującymi kształtami narożników, aby interakcje fokusowania, najeżdżania kursorem i aktywności były widoczne w .gui-split-button
.
Przycisk przełączania okna
Element pomocy „Przycisk wyskakującego okienka” 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, który jest elementem kotwicy .gui-popup-button
, umieszczony absolutnie i semantycznie otaczający listę przycisków.
dodatkowe działania
Fokusowalny przycisk <button>
o nieco mniejszym rozmiarze czcionki niż główny przycisk polecenia zawiera ikonę i styl pasujący 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 .gui-popup
jest elementem podrzędnym przycisku, który je otwiera. Jedynym haczykiem w 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 przedstawiany jako „lista 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ć projektowi charakteru i zastosować ciekawe kolory, 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 przycisku podzielonego
W przypadku tego komponenta otaczającego dobrze sprawdza się typ wyświetlania inline-flex
, ponieważ powinien on 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ę kumulują.
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));
}
}
}
Przy tworzeniu świetnego przycisku należy zwrócić uwagę na mikrointerakcje i drobny detale.
Uwaga dotycząca :focus-visible
Zwróć uwagę, że style przycisków używają wartości :focus-visible
zamiast :focus
. :focus
jest kluczowym elementem tworzenia dostępnego interfejsu użytkownika, ale ma jedną wadę: nie sprawdza, czy użytkownik musi zobaczyć dany element, czy nie.
Film poniżej pokazuje, jak podzielić 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 kursorem z aktywną opcją i aktywnej z zagnieżdżeniem CSS i :is()
selektorem funkcjonalnym:
.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
na ikonie oraz w wyskakującym okienku.
.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 przekształcać animacje 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;
}
}
}
Przyjrzystym użytkownikom uda się zauważyć, że przejścia przezroczystości nadal występują w przypadku 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ładzie ruchomym. Używa właściwości niestandardowych i jednostek względnych, aby była nieco mniejsza i interaktywnie dopasowana do głównego przycisku. Ma też kolorystykę zgodną z elementami marki. Zwróć uwagę, że ikony mają mniejszy kontrast, są cieńsze, a cień ma odcień niebieskiego charakterystyczny dla marki. Podobnie jak w przypadku przycisków, dobre UI i UX to efekt kumulacji 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, który zapewnia korzyści z wypełniania stron logicznych.
– border-end-start-radius
i znajomi będą mieli zaokrąglone rogi w zależności od orientacji 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 myhelper. 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ć fokus do pierwszego (lub ostatnio zaznaczonego) przycisku 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 kliknie przycisk lub dotknie go, albo jeśli przyciski zostaną użyte za pomocą 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