Odtworzenie przycisku Arduino za pomocą SVG i <elementu podświetlonego>

Opublikowany: 2022-03-10
Krótkie podsumowanie ↬ HTML zawiera wiele kontrolek wejściowych i jest mnóstwo bibliotek komponentów, które zawierają wiele standardowych kontrolek, takich jak pola wyboru i przyciski radiowe. Ale co się dzieje, gdy potrzebujesz czegoś niezwykłego?

W tym artykule dowiesz się, jak tworzyć niestandardowe komponenty HTML, które naśladują obiekty fizyczne, takie jak przycisk Arduino. Narysujemy ten komponent w Inkscape od podstaw, zoptymalizujemy wygenerowany kod SVG dla sieci i opakujemy go jako samodzielny komponent sieciowy, używając lekkiej biblioteki lit-element , zwracając szczególną uwagę na dostępność i względy użyteczności mobilnej.

Dzisiaj przeprowadzę Cię przez podróż tworzenia komponentu HTML, który naśladuje chwilowy komponent przycisku, który jest powszechnie używany w Arduino i projektach elektronicznych. Będziemy używać technologii, takich jak SVG, Web Components i lit-element , i nauczymy się, jak sprawić, by przycisk był dostępny za pomocą pewnych sztuczek JavaScript-CSS.

Zaczynajmy!

Od Arduino do HTML: potrzeba komponentu przycisku

Zanim wyruszymy w podróż, zastanówmy się, co zamierzamy stworzyć, a co ważniejsze, dlaczego. Tworzę open-source'owy symulator Arduino w JavaScript o nazwie avr8js. Ten symulator jest w stanie wykonać kod Arduino i będę go używał w serii samouczków i kursów, które uczą twórców programowania dla Arduino.

Sam symulator zajmuje się tylko wykonaniem programu — uruchamia instrukcję kodu po instrukcji i aktualizuje swój stan wewnętrzny oraz bufor pamięci zgodnie z logiką programu. W celu interakcji z programem Arduino należy stworzyć wirtualne elementy elektroniczne, które mogą wysyłać dane wejściowe do symulatora lub reagować na jego wyjścia.

Uruchamianie samego symulatora jest bardzo podobne do uruchamiania JavaScriptu w izolacji. Nie możesz tak naprawdę wchodzić w interakcję z użytkownikiem, chyba że utworzysz również pewne elementy HTML i podłączysz je do kodu JavaScript przez DOM.

Tak więc, oprócz symulatora procesora, pracuję również nad biblioteką komponentów HTML, które naśladują fizyczny sprzęt, zaczynając od dwóch pierwszych komponentów, które można znaleźć w prawie każdym projekcie elektronicznym: diody LED i przycisku.

Dioda LED i elementy przycisku w akcji
Dioda LED i elementy przycisku w akcji

Dioda LED jest stosunkowo prosta, ponieważ ma tylko dwa stany wyjściowe: włączony i wyłączony. Za kulisami wykorzystuje filtr SVG do tworzenia efektu świetlnego.

Bardziej interesujący jest przycisk. Ma również dwa stany, ale musi reagować na dane wejściowe użytkownika i odpowiednio aktualizować swój stan, i właśnie z tego pochodzi wyzwanie, jak wkrótce zobaczymy. Ale najpierw określmy wymagania naszego komponentu, który zamierzamy stworzyć.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Definiowanie wymagań dla przycisku

Nasz komponent będzie przypominał 12mm przycisk. Te przyciski są bardzo popularne w zestawach startowych elektroniki i są dostarczane z nasadkami w wielu kolorach, jak widać na poniższym zdjęciu:

Simon Game z żółtymi, czerwonymi, niebieskimi i zielonymi przyciskami
Simon Game z żółtymi, czerwonymi, niebieskimi i zielonymi przyciskami (duży podgląd)

Pod względem zachowania przycisk powinien mieć dwa stany: wciśnięty i zwolniony. Są one podobne do zdarzeń HTML mousedown/mouseup, ale musimy upewnić się, że przyciski mogą być również używane z urządzeń mobilnych i są dostępne dla użytkowników bez myszy.

Ponieważ będziemy używać stanu przycisku jako wejścia dla Arduino, nie ma potrzeby obsługi zdarzeń „kliknięcia” lub „podwójnego kliknięcia”. To od programu Arduino działającego w symulacji zależy, jak ma działać na stan przycisku, a przyciski fizyczne nie generują zdarzeń kliknięcia.

Jeśli chcesz dowiedzieć się więcej, zapoznaj się z wykładem, który odbyłem z Benjaminem Gruenbaumem na SmashingConf Freiburg w 2019 roku: „Anatomia kliknięcia”.

Podsumowując nasze wymagania, nasz przycisk musi:

  1. wyglądać podobnie do fizycznego przycisku 12 mm;
  2. mają dwa różne stany: wciśnięty i zwolniony i powinny być widoczne wizualnie;
  3. wspierać interakcję z myszą, urządzenia mobilne i być dostępnym dla użytkowników klawiatury;
  4. obsługują różne kolory nasadek (przynajmniej czerwony, zielony, niebieski, żółty, biały i czarny).

Po zdefiniowaniu wymagań możemy rozpocząć pracę nad wdrożeniem.

SVG dla wygranej

Większość komponentów internetowych jest zaimplementowanych przy użyciu kombinacji CSS i HTML. Kiedy potrzebujemy bardziej złożonej grafiki, zwykle używamy obrazów rastrowych, w formacie JPG lub PNG (lub GIF, jeśli czujesz nostalgię).

W naszym przypadku zastosujemy jednak inne podejście: grafikę SVG. SVG nadaje się do skomplikowanej grafiki znacznie łatwiej niż CSS (tak, wiem, możesz tworzyć fascynujące rzeczy za pomocą CSS, ale to nie znaczy, że powinno). Ale nie martw się, nie rezygnujemy całkowicie z CSS. Pomoże nam to w stylizacji przycisków, a ostatecznie nawet w udostępnieniu ich.

SVG ma jeszcze jedną dużą zaletę w porównaniu z obrazami rastrowymi: jest bardzo łatwy do manipulowania z JavaScript i może być stylizowany przez CSS. Oznacza to, że możemy dostarczyć pojedynczy obraz dla przycisku i użyć JavaScript do dostosowania limitu kolorów oraz stylów CSS do wskazania stanu przycisku. Zgrabne, prawda?

Wreszcie, SVG to po prostu dokument XML, który można edytować za pomocą edytorów tekstu i osadzać bezpośrednio w HTML, co czyni go idealnym rozwiązaniem do tworzenia komponentów HTML wielokrotnego użytku. Czy jesteś gotowy, aby narysować nasz przycisk?

Rysowanie przycisku za pomocą Inkscape

Inkscape to moje ulubione narzędzie do tworzenia grafiki wektorowej SVG. Jest bezpłatny i wyposażony w zaawansowane funkcje, takie jak duży zbiór wbudowanych ustawień wstępnych filtrów, śledzenie bitmap i operacje binarne na ścieżkach. Zacząłem używać Inkscape do tworzenia grafiki PCB, ale w ciągu ostatnich dwóch lat zacząłem go używać do większości moich zadań związanych z edycją graficzną.

Rysowanie przycisku w Inkscape jest dość proste. Narysujemy ilustrację przycisku i jego czterech metalowych wyprowadzeń, które łączą go z innymi częściami, w następujący sposób:

  1. Ciemnoszary prostokąt 12×12 mm na plastikowe etui, z lekko zaokrąglonymi rogami, aby było bardziej miękkie.
  2. Mniejszy, jasnoszary prostokąt 10,5×10,5 na metalową osłonę.
  3. Cztery ciemniejsze kółka, po jednym w każdym rogu dla szpilek trzymających razem przycisk.
  4. Pośrodku duże koło, czyli kontur skuwki guzika.
  5. Mniejsze kółko na środku na górze czapki guzika.
  6. Cztery jasnoszare prostokąty w kształcie litery „T” na metalowe wyprowadzenia przycisku.

A wynik, lekko powiększony:

Nasz ręcznie rysowany szkic przycisku
Nasz ręcznie rysowany szkic przycisku (duży podgląd)

Na koniec dodamy trochę magii gradientu SVG do konturu przycisku, aby nadać mu wrażenie 3D:

Dodawanie wypełnienia gradientowego w celu stworzenia efektu 3D
Dodawanie wypełnienia gradientowego do tworzenia efektu 3D (duży podgląd)

No to jedziemy! Mamy wizualizacje, teraz musimy je umieścić w sieci.

Od Inkscape do Web SVG

Jak wspomniałem powyżej, pliki SVG są dość proste do osadzenia w HTML — wystarczy wkleić zawartość pliku SVG do dokumentu HTML, otworzyć go w przeglądarce, a zostanie on wyrenderowany na ekranie. Możesz to zobaczyć w akcji w następującym przykładzie CodePen:

Zobacz przycisk Pen SVG w HTML autorstwa @Uri Shaked

Zobacz przycisk Pen SVG w HTML autorstwa @Uri Shaked

Jednak pliki SVG zapisane z Inkscape zawierają wiele niepotrzebnego bagażu, takiego jak wersja Inkscape i pozycja okna podczas ostatniego zapisania pliku. W wielu przypadkach istnieją również puste elementy, nieużywane gradienty i filtry, które zwiększają rozmiar pliku i utrudniają pracę z nimi w kodzie HTML.

Na szczęście Inkscape może za nas posprzątać większość bałaganu. Oto jak to robisz:

  1. Przejdź do menu Plik i kliknij Wyczyść dokument . (Spowoduje to usunięcie nieużywanych definicji z dokumentu).
  2. Przejdź ponownie do Plik i kliknij Zapisz jako… . Podczas zapisywania wybierz opcję Zoptymalizowany SVG ( *.svg ) z listy rozwijanej Zapisz jako typ .
  3. Zobaczysz okno dialogowe „Zoptymalizowane wyjście SVG” z trzema zakładkami. Zaznacz wszystkie opcje, z wyjątkiem „Zachowaj dane edytora”, „Zachowaj definicje bez odwołań” i „Zachowaj ręcznie utworzone identyfikatory…”.
(duży podgląd)

Usunięcie wszystkich tych rzeczy spowoduje utworzenie mniejszego pliku SVG, z którym łatwiej się pracuje. W moim przypadku plik zmniejszył się z 4593 bajtów do zaledwie 2080 bajtów, mniej niż połowę rozmiaru. W przypadku bardziej złożonych plików SVG może to oznaczać ogromną oszczędność przepustowości i może znacząco wpłynąć na czas ładowania Twojej strony internetowej.

Zoptymalizowany SVG jest również znacznie łatwiejszy do odczytania i zrozumienia. W poniższym fragmencie powinieneś być w stanie łatwo dostrzec dwa prostokąty, które tworzą korpus przycisku:

 <rect width="12" height="12" rx=".44" ry=".44" fill="#464646" stroke-width="1.0003"/> <rect x=".75" y=".75" width="10.5" height="10.5" rx=".211" ry=".211" fill="#eaeaea"/> <g fill="#1b1b1b"> <circle cx="1.767" cy="1.7916" r=".37"/> <circle cx="10.161" cy="1.7916" r=".37"/> <circle cx="10.161" cy="10.197" r=".37"/> <circle cx="1.767" cy="10.197" r=".37"/> </g> <circle cx="6" cy="6" r="3.822" fill="url(#a)"/> <circle cx="6" cy="6" r="2.9" fill="#ff2a2a" stroke="#2f2f2f" stroke-opacity=".47" stroke-width=".08"/>

Możesz jeszcze bardziej skrócić kod, na przykład zmieniając szerokość obrysu pierwszego prostokąta z 1.0003 na tylko 1 . Nie powoduje to znaczącej różnicy w rozmiarze pliku, ale ułatwia odczytanie kodu.

Ogólnie rzecz biorąc, ręczne przepuszczenie wygenerowanego pliku SVG jest zawsze przydatne. W wielu przypadkach można usunąć puste grupy lub zastosować przekształcenia macierzy, a także uprościć współrzędne gradientu, mapując je z „przestrzeni użytkownika w użyciu” (współrzędne globalne) na „obwód ograniczający obiekt” (względem obiektu). Te optymalizacje są opcjonalne, ale otrzymujesz kod, który jest łatwiejszy do zrozumienia i utrzymania.

Od tego momentu odłożymy Inkscape i będziemy pracować z tekstową reprezentacją obrazu SVG.

Tworzenie komponentu internetowego wielokrotnego użytku

Do tej pory otrzymaliśmy grafikę dla naszego przycisku, gotową do wstawienia do naszego symulatora. Możemy łatwo dostosować kolor przycisku, zmieniając atrybut fill mniejszego koła i kolor początkowy gradientu większego koła.

Naszym następnym celem jest przekształcenie naszego przycisku w składnik sieciowy wielokrotnego użytku, który można dostosować, przekazując atrybut color i reagując na interakcję użytkownika (zdarzenia naciśnięcia/zwolnienia). Użyjemy lit-element , małej biblioteki, która upraszcza tworzenie komponentów sieciowych.

lit-element przoduje w tworzeniu małych, samodzielnych bibliotek komponentów. Jest zbudowany na bazie standardu Web Components, który pozwala na wykorzystanie tych komponentów przez dowolną aplikację internetową, niezależnie od użytego frameworka: Angular, React, Vue lub Vanilla JS będą w stanie korzystać z naszego komponentu.

Tworzenie komponentów w lit-element odbywa się przy użyciu składni opartej na klasach, z metodą render() , która zwraca kod HTML elementu. Trochę podobny do Reacta, jeśli go znasz. Jednak w przeciwieństwie do reakcji, lit-element używa standardowych literałów szablonów oznakowanych JavaScript do definiowania zawartości komponentu.

Oto jak możesz stworzyć prosty komponent hello-world :

 import { customElement, html, LitElement } from 'lit-element'; @customElement('hello-world') export class HelloWorldElement extends LitElement { render() { return html` <h1> Hello, World! </h1> `; } }

Ten komponent może być następnie użyty w dowolnym miejscu w kodzie HTML, po prostu pisząc <hello-world></hello-world> .

Uwaga : w rzeczywistości nasz przycisk wymaga nieco więcej kodu: musimy zadeklarować właściwość wejściową dla koloru, używając @property() (i z domyślną wartością red) i wkleić kod SVG do naszego render() , zastępując kolor nasadki przycisku wartością właściwości color (patrz przykład). Ważne bity znajdują się w linii 5, gdzie definiujemy właściwość color: @property() color = 'red'; Również w linii 35 (gdzie używamy tej właściwości do zdefiniowania koloru wypełnienia okręgu, który tworzy nasadkę przycisku), używając składni literału szablonu JavaScript, zapisanej jako ${color} :

 <circle cx="6" cy="6" r="2.9" fill="${color}" stroke="#2f2f2f" stroke-opacity=".47" stroke-width=".08" />

Jak uczynić to interaktywnym

Ostatnim elementem układanki byłoby uczynienie przycisku interaktywnym. Musimy wziąć pod uwagę dwa aspekty: wizualną reakcję na interakcję oraz programową reakcję na interakcję.

W przypadku części wizualnej możemy po prostu odwrócić gradientowe wypełnienie konturu przycisku, co stworzy iluzję naciśnięcia przycisku:

Odwracanie gradientu konturu przycisku
Odwracanie gradientu konturu przycisku (duży podgląd)

Gradient konturu przycisku jest zdefiniowany przez następujący kod SVG, gdzie ${color} jest zastępowany kolorem przycisku przez lit-element , jak wyjaśniono powyżej:

 <linearGradient x1="0" x2="1" y1="0" y2="1"> <stop stop-color="#ffffff" offset="0" /> <stop stop-color="${color}" offset="0.3" /> <stop stop-color="${color}" offset="0.5" /> <stop offset="1" /> </linearGradient>

Jednym z podejść do wyglądu wciśniętego przycisku byłoby zdefiniowanie drugiego gradientu, odwrócenie kolejności kolorów i użycie go jako wypełnienia koła po każdym naciśnięciu przycisku. Jest jednak fajna sztuczka, która pozwala nam ponownie użyć tego samego gradientu: możemy obrócić element svg o 180 stopni za pomocą przekształcenia SVG:

 <circle cx="6" cy="6" r="3.822" fill="url(#a)" transform="rotate(180 6 6)" />

Atrybut transform mówi SVG, że chcemy obrócić okrąg o 180 stopni i że obrót powinien nastąpić wokół punktu (6, 6), który jest środkiem okręgu (określonym przez cx i cy ). Przekształcenia SVG wpływają również na wypełnienie kształtu, więc nasz gradient również zostanie obrócony.

Chcemy odwrócić gradient tylko po naciśnięciu przycisku, więc zamiast dodawać atrybut transform bezpośrednio na elemencie <circle> , tak jak to zrobiliśmy powyżej, w rzeczywistości ustawimy klasę CSS dla tego elementu, a następnie wykorzystamy o tym, że atrybuty SVG można ustawić za pomocą CSS, aczkolwiek przy użyciu nieco innej składni:

 transform: rotate(180deg); transform-origin: 6px 6px;

Te dwie reguły CSS robią dokładnie to samo, co transform , którą mieliśmy powyżej — obróć okrąg o 180 stopni wokół jego środka w (6, 6). Chcemy, aby te reguły były stosowane tylko wtedy, gdy przycisk jest wciśnięty, więc dodamy nazwę klasy CSS do naszego kręgu:

 <circle class="button-contour" cx="6" cy="6" r="3.822" fill="url(#a)" />

A teraz możemy użyć pseudoklasy CSS :active, aby zastosować transformację do button-contour każdym kliknięciu elementu SVG:

 svg:active .button-contour { transform: rotate(180deg); transform-origin: 6px 6px; }

lit-element pozwala nam dołączyć arkusz stylów do naszego komponentu poprzez zadeklarowanie go w statycznej metodzie pobierającej wewnątrz naszej klasy komponentu, używając znakowanego literału szablonu:

 static get styles() { return css` svg:active .button-contour { transform: rotate(180deg); transform-origin: 6px 6px; } `; }

Podobnie jak szablon HTML, ta składnia pozwala nam wstrzykiwać niestandardowe wartości do naszego kodu CSS, nawet jeśli nie jest to potrzebne. lit-element zajmuje się również tworzeniem Shadow DOM dla naszego komponentu, dzięki czemu CSS wpływa tylko na elementy w naszym komponencie i nie przenika do innych części aplikacji.

A co z programowym zachowaniem przycisku po naciśnięciu? Chcemy wywołać zdarzenie, aby użytkownicy naszego komponentu mogli zorientować się, kiedy zmienia się stan przycisku. Jednym ze sposobów, aby to zrobić, jest nasłuchiwanie zdarzeń wciśnięcia i wciśnięcia myszy w elemencie SVG i odpowiednio uruchamianie zdarzeń „naciśnięcie przycisku”/„zwolnienie przycisku”. Tak to wygląda ze składnią lit-element :

 render() { const { color } = this; return html` <svg @mousedown=${() => this.dispatchEvent(new Event('button-press'))} @mouseup=${() => this.dispatchEvent(new Event('button-release'))} ... </svg> `; }

Nie jest to jednak najlepsze rozwiązanie, o czym wkrótce się przekonamy. Ale najpierw spójrz na kod, który otrzymaliśmy do tej pory:

 import { customElement, css, html, LitElement, property } from 'lit-element'; @customElement('wokwi-pushbutton') export class PushbuttonElement extends LitElement { @property() color = 'red'; static get styles() { return css` svg:active .button-contour { transform: rotate(180deg); transform-origin: 6px 6px; } `; } render() { const { color } = this; return html` <svg @mousedown=${() => this.dispatchEvent(new Event('button-press'))} @mouseup=${() => this.dispatchEvent(new Event('button-release'))} width="18mm" height="12mm" version="1.1" viewBox="-3 0 18 12" xmlns="https://www.w3.org/2000/svg" > <defs> <linearGradient x1="0" x2="1" y1="0" y2="1"> <stop stop-color="#ffffff" offset="0" /> <stop stop-color="${color}" offset="0.3" /> <stop stop-color="${color}" offset="0.5" /> <stop offset="1" /> </linearGradient> </defs> <rect x="0" y="0" width="12" height="12" rx=".44" ry=".44" fill="#464646" /> <rect x=".75" y=".75" width="10.5" height="10.5" rx=".211" ry=".211" fill="#eaeaea" /> <g fill="#1b1b1"> <circle cx="1.767" cy="1.7916" r=".37" /> <circle cx="10.161" cy="1.7916" r=".37" /> <circle cx="10.161" cy="10.197" r=".37" /> <circle cx="1.767" cy="10.197" r=".37" /> </g> <g fill="#eaeaea"> <path d="m-0.3538 1.4672c-0.058299 0-0.10523 0.0469-0.10523 0.10522v0.38698h-2.1504c-0.1166 0-0.21045 0.0938-0.21045 0.21045v0.50721c0 0.1166 0.093855 0.21045 0.21045 0.21045h2.1504v0.40101c0 0.0583 0.046928 0.10528 0.10523 0.10528h0.35723v-1.9266z" /> <path d="m-0.35376 8.6067c-0.058299 0-0.10523 0.0469-0.10523 0.10523v0.38697h-2.1504c-0.1166 0-0.21045 0.0939-0.21045 0.21045v0.50721c0 0.1166 0.093855 0.21046 0.21045 0.21046h2.1504v0.401c0 0.0583 0.046928 0.10528 0.10523 0.10528h0.35723v-1.9266z" /> <path d="m12.354 1.4672c0.0583 0 0.10522 0.0469 0.10523 0.10522v0.38698h2.1504c0.1166 0 0.21045 0.0938 0.21045 0.21045v0.50721c0 0.1166-0.09385 0.21045-0.21045 0.21045h-2.1504v0.40101c0 0.0583-0.04693 0.10528-0.10523 0.10528h-0.35723v-1.9266z" /> <path d="m12.354 8.6067c0.0583 0 0.10523 0.0469 0.10523 0.10522v0.38698h2.1504c0.1166 0 0.21045 0.0938 0.21045 0.21045v0.50721c0 0.1166-0.09386 0.21045-0.21045 0.21045h-2.1504v0.40101c0 0.0583-0.04693 0.10528-0.10523 0.10528h-0.35723v-1.9266z" /> </g> <g> <circle class="button-contour" cx="6" cy="6" r="3.822" fill="url(#a)" /> <circle cx="6" cy="6" r="2.9" fill="${color}" stroke="#2f2f2f" stroke-opacity=".47" stroke-width=".08" /> </g> </svg> `; } }
  • Zobacz demo →

Możesz kliknąć każdy z przycisków i zobaczyć, jak reagują. Czerwony ma nawet kilka detektorów zdarzeń (zdefiniowanych w index.html ), więc po kliknięciu na nim powinieneś zobaczyć komunikaty zapisane w konsoli. Ale poczekaj, co jeśli zamiast tego chcesz użyć klawiatury?

Uczynienie komponentu dostępnym i przyjaznym dla urządzeń mobilnych

Hurra! Stworzyliśmy komponent przycisku wielokrotnego użytku z SVG i lit-element !

Zanim podpiszemy się na naszej pracy, jest kilka kwestii, którym powinniśmy się przyjrzeć. Po pierwsze, przycisk nie jest dostępny dla osób korzystających z klawiatury. Ponadto zachowanie na urządzeniach mobilnych jest niespójne — przyciski wydają się być wciśnięte, gdy przytrzymujesz je palcem, ale zdarzenia JavaScript nie są uruchamiane, jeśli przytrzymasz palec dłużej niż jedną sekundę.

Zacznijmy od rozwiązania problemu z klawiaturą. Moglibyśmy sprawić, by przycisk był dostępny z klawiatury, dodając atrybut tabindex do elementu svg, dzięki czemu można na nim skupić się. Moim zdaniem lepszą alternatywą jest po prostu owinięcie przycisku standardowym elementem <button> . Używając standardowego elementu, sprawiamy, że ładnie współpracuje z czytnikami ekranu i innymi technologiami pomocniczymi.

To podejście ma jedną wadę, jak widać poniżej:

Nasz ładny element zamknięty w elemencie przycisku
Nasz ładny komponent zamknięty w elemencie <button> . (duży podgląd)

Element <button> ma wbudowaną stylizację. Można to łatwo naprawić, stosując niektóre CSS, aby usunąć te style:

 button { border: none; background: none; padding: 0; margin: 0; text-decoration: none; -webkit-appearance: none; -moz-appearance: none; } button:active .button-contour { transform: rotate(180deg); transform-origin: 6px 6px; }

Zauważ, że zastąpiliśmy również selektor, który odwraca siatkę konturu przycisków, używając button:active zamiast svg:active . Gwarantuje to, że styl naciśnięcia przycisku będzie stosowany za każdym razem, gdy zostanie naciśnięty rzeczywisty element <button> , niezależnie od używanego urządzenia wejściowego.

Możemy nawet uczynić nasz komponent bardziej przyjaznym dla czytników ekranu, dodając atrybut aria-label , który zawiera kolor przycisku:

 <button aria-label="${color} pushbutton">

Jest jeszcze jedna sprawa do rozwiązania: zdarzenia „naciśnięcia przycisku” i „zwolnienia przycisku”. Idealnie, chcielibyśmy je uruchomić w oparciu o pseudoklasę CSS :active przycisku, tak jak zrobiliśmy to w powyższym CSS. Innymi słowy, chcielibyśmy wywołać zdarzenie „naciśnięcie przycisku”, gdy przycisk staje się :active , a zdarzenie „zwolnienie przycisku” uruchamiane, gdy jest :not(:active) .

Ale jak słuchać pseudo-klasy CSS z JavaScript?

Okazuje się, że to nie jest takie proste. Zadałem to pytanie społeczności JavaScript Israel i ostatecznie wykopałem jeden pomysł, który wyszedł z niekończącego się wątku: użyj selektora :active , aby uruchomić super-krótką animację CSS, a następnie mogę słuchać jej z JavaScript za pomocą animationstart wydarzenie.

Szybki eksperyment CodePen udowodnił, że to faktycznie działa niezawodnie. O ile spodobało mi się wyrafinowanie tego pomysłu, zdecydowałem się na inne, prostsze rozwiązanie. Zdarzenie animationstart nie jest dostępne w Edge i iOS Safari, a wyzwalanie animacji CSS tylko w celu wykrycia zmiany stanu przycisku nie brzmi jak właściwy sposób.

Zamiast tego dodamy trzy pary detektorów zdarzeń do elementu <button> : mousedown/mouseup dla myszy, touchstart/touchend dla urządzeń mobilnych oraz keyup/keydown dla klawiatury. Moim zdaniem nie jest to najbardziej eleganckie rozwiązanie, ale działa i działa we wszystkich przeglądarkach.

 <button aria-label="${color} pushbutton" @mousedown=${this.down} @mouseup=${this.up} @touchstart=${this.down} @touchend=${this.up} @keydown=${(e: KeyboardEvent) => e.keyCode === SPACE_KEY && this.down()} @keyup=${(e: KeyboardEvent) => e.keyCode === SPACE_KEY && this.up()} >

Gdzie SPACE_KEY jest stałą równą 32, a up / down to dwie metody klasy, które wywołują zdarzenia button-press button-release :

 @property() pressed = false; private down() { if (!this.pressed) { this.pressed = true; this.dispatchEvent(new Event('button-press')); } } private up() { if (this.pressed) { this.pressed = false; this.dispatchEvent(new Event('button-release')); } }
  • Pełny kod źródłowy można znaleźć tutaj.

Zrobiliśmy to!

To była dość długa podróż, która zaczęła się od nakreślenia wymagań i narysowania ilustracji przycisku w Inkscape, przeszła przez konwersję naszego pliku SVG na komponent sieciowy wielokrotnego użytku za pomocą lit-element , a po upewnieniu się, że jest dostępny i przyjazny dla urządzeń mobilnych, skończyło się na prawie 100 liniach kodu zachwycającego komponentu wirtualnego przycisku.

Ten przycisk jest tylko pojedynczym komponentem w otwartej bibliotece wirtualnych komponentów elektronicznych, które tworzę. Zapraszamy do zapoznania się z kodem źródłowym lub do zapoznania się z internetowym Storybookiem, w którym można zobaczyć i wchodzić w interakcję ze wszystkimi dostępnymi komponentami.

I na koniec, jeśli jesteś zainteresowany Arduino, spójrz na kurs programowania Simon for Arduino, który aktualnie tworzę, gdzie możesz również zobaczyć przycisk w akcji.

Do następnego razu!