Podstawowe informacje o tworzeniu elastycznych i łatwo dostępnych komponentów przełącznika.
W tym poście przedstawię pomysł na projektowanie komponentów z przełącznikami. Wypróbuj wersję demonstracyjną.
.Jeśli wolisz film, oto wersja tego posta w YouTube:
Omówienie
Przełącznik działa podobnie do pola wyboru , ale wyraźnie reprezentuje stan logiczny (włącz) i wyłączony.
W tej wersji demonstracyjnej większość kodu wykorzystuje <input type="checkbox" role="switch">
tej funkcji. Zaletą takiego rozwiązania jest to, że nie wymaga stosowania CSS ani JavaScriptu,
w pełni funkcjonalne i łatwo dostępne. Ładowanie CSS zapewnia obsługę tekstu od prawej do lewej
języki, pionowość, animacje i inne elementy. Wczytywanie JavaScriptu powoduje przełączenie
jest bardziej czytelny i materialny.
Właściwości niestandardowe
Poniższe zmienne przedstawiają różne części przełącznika i ich
. Jako klasa najwyższego poziomu .gui-switch
zawiera użyte właściwości niestandardowe
w obrębie elementów podrzędnych i punkty wejścia
i personalizacji reklam.
Monitoruj
Długość (--track-size
), dopełnienie i 2 kolory:
.gui-switch {
--track-size: calc(var(--thumb-size) * 2);
--track-padding: 2px;
--track-inactive: hsl(80 0% 80%);
--track-active: hsl(80 60% 45%);
--track-color-inactive: var(--track-inactive);
--track-color-active: var(--track-active);
@media (prefers-color-scheme: dark) {
--track-inactive: hsl(80 0% 35%);
--track-active: hsl(80 60% 60%);
}
}
Miniatura
Rozmiar, kolor tła i kolory wyróżnienia podczas interakcji:
.gui-switch {
--thumb-size: 2rem;
--thumb: hsl(0 0% 100%);
--thumb-highlight: hsl(0 0% 0% / 25%);
--thumb-color: var(--thumb);
--thumb-color-highlight: var(--thumb-highlight);
@media (prefers-color-scheme: dark) {
--thumb: hsl(0 0% 5%);
--thumb-highlight: hsl(0 0% 100% / 25%);
}
}
Ograniczony ruch
Aby dodać jasny alias i zmniejszyć liczbę powtórzeń, użytkownik ze zmniejszonym ustawieniem ruchu zapytania o media można umieścić we właściwości niestandardowej za pomocą metody PostCSS wtyczka na podstawie tej wersji roboczej specyfikacja w sekcji Zapytania o multimedia 5:
@custom-media --motionOK (prefers-reduced-motion: no-preference);
Markup
Mój element <input type="checkbox" role="switch">
ma być opakowany
<label>
, grupuję ich relacje w pakiecie, aby uniknąć powiązania pola wyboru z etykietą
są niejednoznaczne, a użytkownik może wejść w interakcję z etykietą,
przełączaj dane wejściowe.
<label for="switch" class="gui-switch">
Label text
<input type="checkbox" role="switch" id="switch">
</label>
Urządzenie <input type="checkbox">
ma gotowe funkcje
Interfejs API
i stan.
przeglądarka zarządza
checked
właściwość i dane wejściowe
wydarzenia
Na przykład oninput
i onchanged
.
Układy
Flexbox, siatka i niestandardowa właściwości mają kluczowe znaczenie. w utrzymaniu stylów tego komponentu. Centralizują wartości, nadają nazwy aby w inny sposób niejednoznaczne obliczenia lub obszary, i umożliwić utworzenie niewielkiej właściwości niestandardowej. Interfejs API umożliwiający łatwe dostosowywanie komponentów.
.gui-switch
Układ najwyższego poziomu przełącznika to Flexbox. Klasa .gui-switch
zawiera
z prywatnymi i publicznymi właściwościami niestandardowymi używanymi przez elementy podrzędne do obliczania
układy.
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
Rozszerzanie i modyfikowanie układu Flexbox jest jak zmienianie układu Flexbox.
Aby na przykład umieścić etykiety nad lub pod przełącznikiem lub zmienić
flex-direction
:
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
Monitoruj
Pole wyboru jest stylizowane na ścieżkę przełącznikową – zostaje usunięte jej normalne pole
appearance: checkbox
i podaj w zamian własny rozmiar:
.gui-switch > input {
appearance: none;
inline-size: var(--track-size);
block-size: var(--thumb-size);
padding: var(--track-padding);
flex-shrink: 0;
display: grid;
align-items: center;
grid: [track] 1fr / [track] 1fr;
}
Ścieżka tworzy również osobny obszar siatki jednokomórkowej, który można kliknąć, reklamację.
Miniatura
Styl appearance: none
usuwa też wizualny znacznik wyboru dostarczony przez komponent
przeglądarki. Ten komponent używa
pseudoelement i :checked
pseudoklasa na danych wejściowych do
zastąpić ten wizualny wskaźnik.
Kciuk to pseudoelementowy element podrzędny powiązany z elementami input[type="checkbox"]
i
nakładają się na ścieżkę, a nie pod nią, przejmując obszar siatki.
track
:
.gui-switch > input::before {
content: "";
grid-area: track;
inline-size: var(--thumb-size);
block-size: var(--thumb-size);
}
Style
Właściwości niestandardowe umożliwiają uniwersalny komponent przełącznika, który dostosowuje się do koloru schematy, języki zapisywane od prawej do lewej i preferencje ruchu.
Style interakcji dotykowe
Na urządzeniach mobilnych przeglądarki dodają funkcje zaznaczania dotykiem i zaznaczania tekstu do etykiet i
danych wejściowych. Niekorzystnie wpłynęło to na styl i opinię dotyczącą interakcji wizualnej,
potrzebny przełącznik. Za pomocą kilku wierszy CSS mogę usunąć te efekty i dodać
mój własny styl cursor: pointer
:
.gui-switch {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
Nie zawsze warto usuwać takie style, ponieważ mogą być wartościowe, interakcji z reklamą. Pamiętaj, aby w przypadku ich usunięcia udostępnić niestandardowe alternatywne wersje.
Monitoruj
Style tego elementu dotyczą głównie jego kształtu i koloru, do których ma dostęp.
z elementu nadrzędnego .gui-switch
przez
kaskadowym.
.gui-switch > input {
appearance: none;
border: none;
outline-offset: 5px;
box-sizing: content-box;
padding: var(--track-padding);
background: var(--track-color-inactive);
inline-size: var(--track-size);
block-size: var(--thumb-size);
border-radius: var(--track-size);
}
Istnieją cztery różne opcje dostosowywania ścieżki przełącznika.
właściwości niestandardowe. Dodano pole border: none
, ponieważ appearance: none
nie
usuń obramowanie z pola wyboru we wszystkich przeglądarkach.
Miniatura
Element kciuka znajduje się już po prawej stronie track
, ale wymaga stylów okręgu:
.gui-switch > input::before {
background: var(--thumb-color);
border-radius: 50%;
}
Interakcja
Wykorzystaj właściwości niestandardowe, aby przygotować się do interakcji, które będą powodowały najechanie kursorem. podświetlenia i zmiany pozycji kciuka. Zgodnie z preferencjami użytkownika zaznaczono przed przeniesieniem stylem zaznaczenia.
.gui-switch > input::before {
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
Pozycja kciuka
Właściwości niestandardowe udostępniają jeden mechanizm źródłowy do umieszczania kciuka w
ścieżkę dźwiękową. Do dyspozycji gości są rozmiary torów i kciuków, których używamy
pozwalające zachować prawidłową pozycję kciuka w obrębie ścieżki:
0%
i 100%
.
Element input
jest właścicielem zmiennej pozycji --thumb-position
i kciuka
pseudoelement używa go jako pozycji translateX
:
.gui-switch > input {
--thumb-position: 0%;
}
.gui-switch > input::before {
transform: translateX(var(--thumb-position));
}
Teraz możesz swobodnie zmieniać --thumb-position
z CSS i pseudoklas.
dostępne w elementach pól wyboru. Ponieważ wcześniej warunkowo ustawiliśmy transition: transform
var(--thumb-transition-duration) ease
dla tego elementu, te zmiany
może być animowany po zmianie:
/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
--thumb-position: calc(var(--track-size) - 100%);
}
/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
--thumb-position: calc(
(var(--track-size) / 2) - (var(--thumb-size) / 2)
);
}
Ta odrębna administracja mi się podoba. Elementem kciuka jest
dotyczy tylko jednego stylu, pozycji translateX
. Dane wejściowe mogą zarządzać wszystkimi
ze złożonością i obliczeniami.
Branża
Obsługiwano za pomocą klasy modyfikatora -vertical
, która dodaje rotację z
CSS przekształca się do elementu input
.
Obrócony element 3D nie zmienia jednak ogólnej wysokości komponentu.
co może zniechęcać do tworzenia bloków. Uwzględnij to za pomocą: --track-size
i
--track-padding
zmiennych. Oblicz minimalną ilość miejsca wymaganą dla
przycisk pionowy, który wyświetla się w układzie zgodnie z oczekiwaniami:
.gui-switch.-vertical {
min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));
& > input {
transform: rotate(-90deg);
}
}
(RTL) od prawej do lewej
Razem z przyjaciółką CSS, Elad Schecter, stworzyliśmy prototyp wysuwane menu boczne z przekształceniami CSS obsługiwanymi od prawej do lewej , odwrotnie . Zrobiliśmy to, ponieważ w CSS nie ma żadnych logicznych przekształceń właściwości, i być może nigdy nie będzie. Elad wpadł na doskonały pomysł wykorzystania wartości właściwości niestandardowej, odwrócić wartości procentowe w celu umożliwienia zarządzania jedną lokalizacją z własnej przekształceń logicznych. Użyłem tej samej techniki w tym przełączniku i Wspaniale:
.gui-switch {
--isLTR: 1;
&:dir(rtl) {
--isLTR: -1;
}
}
Właściwość niestandardowa o nazwie --isLTR
początkowo zawiera wartość 1
, co oznacza, że jest
true
, bo nasz układ to domyślnie od lewej do prawej. Następnie za pomocą arkusza CSS
pseudoklasa :dir()
,
wartość jest ustawiona na -1
, gdy komponent jest w układzie od prawej do lewej.
Wykonaj działanie funkcji --isLTR
, używając jej w elemencie calc()
wewnątrz przekształcenia:
.gui-switch.-vertical > input {
transform: rotate(-90deg);
transform: rotate(calc(90deg * var(--isLTR) * -1));
}
Teraz obrót przełącznika w pionie uwzględnia przeciwną pozycję boków wymagane przez układ od prawej do lewej.
Przekształcanie elementu translateX
na pseudoelementu kciuka również wymaga aktualizacji
uwzględnij odwrotny wymóg:
.gui-switch > input:checked {
--thumb-position: calc(var(--track-size) - 100%);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
.gui-switch > input:indeterminate {
--thumb-position: calc(
(var(--track-size) / 2) - (var(--thumb-size) / 2)
);
--thumb-position: calc(
((var(--track-size) / 2) - (var(--thumb-size) / 2))
* var(--isLTR)
);
}
Takie podejście nie sprawdzi się w przypadku takich koncepcji jak logiczny kod CSS. zapewnia pewne przekształcenia, zasady DRY dla wielu i przypadków użycia.
Stany
Korzystanie z wbudowanej usługi input[type="checkbox"]
nie byłoby pełne bez
obsługa różnych stanów: :checked
, :disabled
,
:indeterminate
i :hover
. :focus
został celowo pozostawiony sam, z
tylko jej przesunięcia; pierścień ostrości wyglądał świetnie w Firefoksie.
Safari:
Zaznaczono
<label for="switch-checked" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>
Ten stan reprezentuje stan: on
. W takim stanie wejściowa wartość „track”
kolor tła jest aktywny, a kciuk jest ustawiony na
na końcu”.
.gui-switch > input:checked {
background: var(--track-color-active);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
Wyłączono
<label for="switch-disabled" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>
Przycisk :disabled
nie tylko wygląda inaczej, ale powinien też sprawić,
elementu stałego.Niezmienność interakcji jest bezpłatna w przeglądarce, ale
stany wizualne wymagają stylów ze względu na użycie atrybutu appearance: none
.
.gui-switch > input:disabled {
cursor: not-allowed;
--thumb-color: transparent;
&::before {
cursor: not-allowed;
box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);
@media (prefers-color-scheme: dark) { & {
box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
}}
}
}
Ten stan jest trudny, ponieważ wymaga ciemnych i jasnych motywów z wyłączonymi i wyłączonymi wybranych stanów. Stylistycznie wybrałem minimalistyczne style dla tych stanów, aby ułatwić i ciężka praca z wykorzystaniem kombinacji stylów.
Nieokreślona
Stan, który często zapominamy, to :indeterminate
, w którym pole wyboru nie jest żadnym z nich
zaznaczono lub odznaczono. Ciekawe, swobodne i swobodne. Dobra
że stany logiczne
mogą niejawnie przełączać się między stanami.
Trudno jest ustawić pole wyboru tak, by było ono nieokreślone, ponieważ może je ustawić tylko JavaScript:
<label for="switch-indeterminate" class="gui-switch">
Indeterminate
<input type="checkbox" role="switch" id="switch-indeterminate">
<script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>
Jako że państwo jest dla mnie niepozorne i przyjazne, uznałem za odpowiednie, ustaw kciuk w pozycji środkowej:
.gui-switch > input:indeterminate {
--thumb-position: calc(
calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
* var(--isLTR)
);
}
Najechanie
Interakcje po najechaniu kursorem powinny zapewniać wizualną obsługę połączonego interfejsu wskazówkę w kierunku interaktywnego interfejsu. Ten przełącznik podświetla kciuk w górę półprzezroczysty pierścień po najechaniu kursorem na etykietę lub dane wejściowe. Po najechaniu kursorem animacja wskazuje kierunek interaktywnego elementu kciuka.
Najciekawsze momenty efekt kończy się za pomocą funkcji box-shadow
. Gdy najedziesz kursorem na wyłączone wejście, zwiększ rozmiar elementu --highlight-size
. Jeśli ruchowi użytkownika nie przeszkadza ruch, przesuwamy element box-shadow
i widzimy, jak rośnie. Jeśli ruch nie odpowiada ruchom, wyróżnienie pojawia się natychmiast:
.gui-switch > input::before {
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
.gui-switch > input:not(:disabled):hover::before {
--highlight-size: .5rem;
}
JavaScript
Uważam, że interfejs przełączników może wydawać się niesamowicie emulowany zwłaszcza z okręgiem wewnątrz ścieżki. iOS ma rację wystarczy przeciągnąć je na boki. mają możliwość wyboru. I na odwrót: element interfejsu może wydawać się nieaktywny, jeśli zostanie wykonane gesty przeciągania ale nic się nie dzieje.
Przeciągane kciuki
Pseudoelement kciuka otrzymuje swoją pozycję z elementu .gui-switch > input
o zakresie var(--thumb-position)
, JavaScript może dostarczyć wartość stylu wbudowanego w
dynamicznie zmienia pozycję kciuka, tak by podążała za nim
gest wskaźnika. Po zwolnieniu wskaźnika usuń style wbudowane,
określić za pomocą właściwości niestandardowej, czy przeciągnięcie było bliżej wyłączenia czy włączenia.
--thumb-position
To jest podstawa rozwiązania. zdarzenia wskaźnika
warunkowo śledzi pozycje wskaźników, by modyfikować właściwości niestandardowe CSS.
Ponieważ przed wyświetleniem skryptu komponent był już w pełni sprawny utrzymanie dotychczasowego zachowania wymaga sporo pracy, kliknij etykietę, by przełączyć dane wejściowe. Nasz JavaScript nie powinien dodawać funkcji kosztów istniejących funkcji.
touch-action
Przeciąganie to niestandardowy gest, dzięki któremu doskonale sprawdza się
touch-action
– korzyści. W przypadku tego przełącznika gest poziomy powinien
obsługiwany przez nasz skrypt lub pionowy gest
wersji. Dzięki touch-action
możemy wskazać przeglądarce, które gesty ma obsługiwać
ten element, dzięki czemu skrypt może obsłużyć gest bez konkurencji.
Ten kod CSS informuje przeglądarkę, że gdy gest wskaźnika zaczyna się od w obrębie tej ścieżki przełącznika, obsługiwać gesty pionowe, nie rób nic w poziomie te:
.gui-switch > input {
touch-action: pan-y;
}
Pożądanym wynikiem jest gest poziomy, który nie powoduje też przesuwania ani przewijania stronę. Wskaźnik może przewijać w pionie, zaczynając od wpisanego tekstu, a następnie przewijając a strony poziome są obsługiwane niestandardowe.
Narzędzia związane ze stylem wartości Pixela
Podczas konfiguracji i przeciągania należy pobierać różne obliczone wartości liczbowe.
od elementów. Poniższe funkcje JavaScriptu zwracają obliczone wartości pikseli
dla właściwości CSS. Jest używany w skrypcie konfiguracji w ten sposób
getStyle(checkbox, 'padding-left')
const getStyle = (element, prop) => {
return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}
const getPseudoStyle = (element, prop) => {
return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}
export {
getStyle,
getPseudoStyle,
}
Zwróć uwagę, że funkcja window.getComputedStyle()
akceptuje drugi argument – docelowy pseudoelement. Całkiem dobrze, że JavaScript może odczytywać tak wiele wartości z elementów, nawet z pseudoelementów.
dragging
To najważniejszy moment logiki drag queens. Należy pamiętać o kilku rzeczach. z modułu obsługi zdarzeń funkcji:
const dragging = event => {
if (!state.activethumb) return
let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
let directionality = getStyle(state.activethumb, '--isLTR')
let track = (directionality === -1)
? (state.activethumb.clientWidth * -1) + thumbsize + padding
: 0
let pos = Math.round(event.offsetX - thumbsize / 2)
if (pos < bounds.lower) pos = 0
if (pos > bounds.upper) pos = bounds.upper
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}
Baner powitalny skryptu to state.activethumb
, mały okrąg, którym jest ten skrypt
wraz ze wskaźnikiem. Obiekt switches
to Map()
, gdzie
są przypisane do .gui-switch
, a wartości są przechowywane w pamięci podręcznej i rozmiarach, które zachowują
i efektywność skryptu. Tekst pisany od prawej do lewej jest obsługiwany przy użyciu tej samej właściwości niestandardowej
że CSS to --isLTR
i może go użyć do odwrócenia logiki i kontynuowania
z obsługą tekstu od prawej do lewej. Pole event.offsetX
też jest wartościowe, ponieważ zawiera wartość delta
przydatna do umieszczenia kciuka.
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
Ostatni wiersz kodu CSS ustawia właściwość niestandardową używaną przez element kciuka. Ten
w przeciwnym razie przypisanie wartości spowodowałoby przejście z biegiem czasu, ale poprzedni wskaźnik
zdarzenie tymczasowo ustawiło --thumb-transition-duration
na 0s
, usuwając co
co wymagałoby powolnej interakcji.
dragEnd
Aby użytkownik mógł przeciągnąć poza przełącznik i puścić go, Wymagane zarejestrowane zdarzenie okna globalnego:
window.addEventListener('pointerup', event => {
if (!state.activethumb) return
dragEnd(event)
})
Uważam, że bardzo ważne jest, aby użytkownik miał swobodę przeciągania aby to uwzględnić. Obsługa tego systemu nie była zbyt prosta. tej zmiany wymaga jednak uważnego przemyślenia jej w procesie proces tworzenia konta.
const dragEnd = event => {
if (!state.activethumb) return
state.activethumb.checked = determineChecked()
if (state.activethumb.indeterminate)
state.activethumb.indeterminate = false
state.activethumb.style.removeProperty('--thumb-transition-duration')
state.activethumb.style.removeProperty('--thumb-position')
state.activethumb.removeEventListener('pointermove', dragging)
state.activethumb = null
padRelease()
}
Ukończono interakcję z elementem, czas na ustawienie danych wejściowych
i usuń wszystkie zdarzenia gestów. Pole wyboru jest zmieniane na
state.activethumb.checked = determineChecked()
determineChecked()
Ta funkcja, wywoływana przez funkcję dragEnd
, wskazuje miejsce prądu kciuka
znajduje się w granicach ścieżki i zwraca wartość „prawda”, jeśli jest równa lub większa
w połowie ścieżki:
const determineChecked = () => {
let {bounds} = switches.get(state.activethumb.parentElement)
let curpos =
Math.abs(
parseInt(
state.activethumb.style.getPropertyValue('--thumb-position')))
if (!curpos) {
curpos = state.activethumb.checked
? bounds.lower
: bounds.upper
}
return curpos >= bounds.middle
}
Dodatkowe uwagi
Gest przeciągania powoduje trochę brak kodu z powodu początkowej struktury HTML
Przede wszystkim chodzi o umieszczenie danych wejściowych w etykiecie. etykietę w roli elementu nadrzędnego,
, otrzyma interakcje po kliknięciu. Na koniec
dragEnd
– zdarzenie padRelease()
brzmi dziwnie,
.
const padRelease = () => {
state.recentlyDragged = true
setTimeout(_ => {
state.recentlyDragged = false
}, 300)
}
Ma to na celu uwzględnienie przyczyny późniejszego kliknięcia etykiety, ponieważ spowodowałoby to usunięcie zaznaczenia. interakcji, którą wykonał użytkownik.
Gdybym miał to zrobić ponownie, mogę rozważyć dostosowanie DOM za pomocą JavaScriptu. podczas uaktualniania UX, czyli stworzyć element, który obsługuje kliknięcia etykiet. i nie walczy z wbudowanymi funkcjami.
Ten rodzaj JavaScriptu najbardziej lubię pisać i nie chcę nim zarządzać dymki zdarzeń warunkowych:
const preventBubbles = event => {
if (state.recentlyDragged)
event.preventDefault() && event.stopPropagation()
}
Podsumowanie
Ten komponent nastolatka łącznika okazał się najciekawszym elementem spośród wszystkich wyzwań GUI do tej pory! Wiesz już, jak to zrobiłem. Jak Ty? 🙂
Stosujmy różne podejścia i poznajmy sposoby budowania obecności w internecie. Utwórz wersję demonstracyjną, a potem dodaj linki do funkcji tweetuj mi. znajdziesz poniżej w sekcji z remiksami na karcie Społeczność.
Remiksy utworzone przez społeczność
- @KonstantinRouda z elementem niestandardowym: demo i code.
- @jhvanderschee z przyciskiem: Codepen.
Zasoby
Kod źródłowy .gui-switch
znajdziesz w
GitHub.