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