Neuerstellung des Arduino-Tasters mit SVG und <lit-element>

Veröffentlicht: 2022-03-10
Kurze Zusammenfassung ↬ HTML wird mit einer Reihe von Eingabesteuerelementen geliefert, und es gibt unzählige Komponentenbibliotheken, die viele Standardsteuerelemente wie Kontrollkästchen und Optionsfelder enthalten. Aber was passiert, wenn Sie etwas Ungewöhnliches brauchen?

In diesem Artikel erfahren Sie, wie Sie benutzerdefinierte HTML-Komponenten erstellen, die physische Objekte wie den Arduino Pushbutton nachahmen. Wir zeichnen die Komponente in Inkscape von Grund auf neu, optimieren den generierten SVG-Code für das Web und verpacken sie als eigenständige Webkomponente mithilfe der leichtgewichtigen lit-element Bibliothek, wobei wir besonderes Augenmerk auf Zugänglichkeit und mobile Nutzbarkeit legen.

Heute werde ich Sie durch die Reise der Erstellung einer HTML-Komponente führen, die eine momentane Drucktastenkomponente nachahmt, die häufig mit Arduino und in elektronischen Projekten verwendet wird. Wir werden Technologien wie SVG, Webkomponenten und lit-element und lernen, wie man die Schaltfläche durch einige JavaScript-CSS-Tricks zugänglich macht.

Lasst uns beginnen!

Von Arduino zu HTML: Die Notwendigkeit einer Drucktastenkomponente

Bevor wir uns auf die Reise begeben, lassen Sie uns untersuchen, was wir erstellen werden, und was noch wichtiger ist, warum. Ich erstelle einen Open-Source-Arduino-Simulator in JavaScript namens avr8js. Dieser Simulator kann Arduino-Code ausführen und ich werde ihn in einer Reihe von Tutorials und Kursen verwenden, die Entwicklern beibringen, wie man für Arduino programmiert.

Der Simulator selbst kümmert sich nur um die Programmausführung – er führt den Code Anweisung für Anweisung aus und aktualisiert seinen internen Zustand und einen Speicherpuffer entsprechend der Programmlogik. Um mit dem Arduino-Programm zu interagieren, müssen Sie einige virtuelle elektronische Komponenten erstellen, die Eingaben an den Simulator senden oder auf seine Ausgaben reagieren können.

Das Ausführen des Simulators allein ist dem isolierten Ausführen von JavaScript sehr ähnlich. Sie können nicht wirklich mit dem Benutzer interagieren, es sei denn, Sie erstellen auch einige HTML-Elemente und verknüpfen sie über das DOM mit dem JavaScript-Code.

Daher arbeite ich neben dem Simulator des Prozessors auch an einer Bibliothek von HTML-Komponenten, die physische Hardware nachahmen, beginnend mit den ersten beiden Komponenten, die Sie in fast jedem Elektronikprojekt finden: eine LED und ein Taster.

Die LED und die Tasterelemente in Aktion
Die LED und die Tasterelemente in Aktion

Die LED ist relativ einfach, da sie nur zwei Ausgangszustände hat: ein und aus. Hinter den Kulissen verwendet es einen SVG-Filter, um den Lichteffekt zu erzeugen.

Der Druckknopf ist interessanter. Es hat auch zwei Zustände, aber es muss auf Benutzereingaben reagieren und seinen Zustand entsprechend aktualisieren, und hier kommt die Herausforderung, wie wir gleich sehen werden. Aber lassen Sie uns zuerst die Anforderungen an unsere Komponente festlegen, die wir erstellen werden.

Mehr nach dem Sprung! Lesen Sie unten weiter ↓

Definition der Anforderungen an die Drucktaste

Unsere Komponente wird einem 12-mm-Druckknopf ähneln. Diese Tasten sind in Elektronik-Starterkits sehr verbreitet und werden mit Kappen in mehreren Farben geliefert, wie Sie auf dem Foto unten sehen können:

Simon-Spiel mit gelben, roten, blauen und grünen Drucktasten
Simon-Spiel mit gelben, roten, blauen und grünen Drucktasten (große Vorschau)

Hinsichtlich des Verhaltens sollte der Taster zwei Zustände haben: gedrückt und losgelassen. Diese ähneln den Mousedown/Mouseup-HTML-Events, allerdings muss darauf geachtet werden, dass die Pushbuttons auch von mobilen Geräten aus nutzbar und für Benutzer ohne Maus zugänglich sind.

Da wir den Zustand der Drucktaste als Eingabe für Arduino verwenden werden, besteht keine Notwendigkeit, "Klick"- oder "Doppelklick"-Ereignisse zu unterstützen. Es liegt am Arduino-Programm, das in der Simulation ausgeführt wird, zu entscheiden, wie es auf den Zustand der Schaltfläche reagieren soll, und physische Schaltflächen erzeugen keine Klickereignisse.

Wenn Sie mehr erfahren möchten, sehen Sie sich einen Vortrag an, den ich 2019 mit Benjamin Gruenbaum auf der SmashingConf Freiburg gehalten habe: „Anatomy of a Click“.

Um unsere Anforderungen zusammenzufassen, muss unser Taster:

  1. sehen ähnlich aus wie der physische 12-mm-Druckknopf;
  2. zwei unterschiedliche Zustände haben: gedrückt und losgelassen, und sie sollten visuell erkennbar sein;
  3. Mausinteraktion und mobile Geräte unterstützen und für Tastaturbenutzer zugänglich sein;
  4. unterstützen verschiedene Kappenfarben (mindestens rot, grün, blau, gelb, weiß und schwarz).

Nachdem wir nun die Anforderungen definiert haben, können wir mit der Umsetzung beginnen.

SVG für den Sieg

Die meisten Webkomponenten werden mit einer Kombination aus CSS und HTML implementiert. Wenn wir komplexere Grafiken benötigen, verwenden wir normalerweise Rasterbilder im JPG- oder PNG-Format (oder GIF, wenn Sie sich nostalgisch fühlen).

In unserem Fall verwenden wir jedoch einen anderen Ansatz: SVG-Grafiken. SVG eignet sich viel einfacher für komplexe Grafiken als CSS (ja, ich weiß, Sie können mit CSS faszinierende Dinge erstellen, aber das bedeutet nicht, dass es sollte). Aber keine Sorge, wir verzichten nicht ganz auf CSS. Es wird uns helfen, die Drucktasten zu gestalten und sie schließlich zugänglich zu machen.

SVG hat im Vergleich zu Rastergrafikbildern einen weiteren großen Vorteil: Es ist sehr einfach von JavaScript aus zu manipulieren und kann über CSS gestaltet werden. Das bedeutet, dass wir ein einzelnes Bild für die Schaltfläche bereitstellen und JavaScript verwenden können, um die Farbkappe und CSS-Stile anzupassen, um den Status der Schaltfläche anzuzeigen. Ordentlich, nicht wahr?

Schließlich ist SVG nur ein XML-Dokument, das mit Texteditoren bearbeitet und direkt in HTML eingebettet werden kann, was es zu einer perfekten Lösung zum Erstellen wiederverwendbarer HTML-Komponenten macht. Sind Sie bereit, unseren Taster zu zeichnen?

Zeichnen der Drucktaste mit Inkscape

Inkscape ist mein Lieblingswerkzeug zum Erstellen von SVG-Vektorgrafiken. Es ist kostenlos und vollgepackt mit leistungsstarken Funktionen, wie z. B. einer großen Sammlung integrierter Filtervoreinstellungen, Bitmap-Verfolgung und binären Pfadoperationen. Ich habe angefangen, Inkscape zum Erstellen von PCB-Grafiken zu verwenden, aber in den letzten zwei Jahren habe ich damit begonnen, es für die meisten meiner Grafikbearbeitungsaufgaben zu verwenden.

Das Zeichnen der Drucktaste in Inkscape ist ziemlich einfach. Wir werden eine Draufsicht des Knopfes und seiner vier Metallleitungen, die ihn mit anderen Teilen verbinden, wie folgt zeichnen:

  1. 12 × 12 mm dunkelgraues Rechteck für das Kunststoffgehäuse mit leicht abgerundeten Ecken, um es weicher zu machen.
  2. Kleineres, 10,5 × 10,5 hellgraues Rechteck für die Metallabdeckung.
  3. Vier dunklere Kreise, einer in jeder Ecke für die Stifte, die den Knopf zusammenhalten.
  4. Ein großer Kreis in der Mitte, das ist die Kontur der Knopfkappe.
  5. Ein kleinerer Kreis in der Mitte für die Oberseite der Knopfkappe.
  6. Vier hellgraue Rechtecke in „T“-Form für die Metallleitungen des Tasters.

Und das Ergebnis, leicht vergrößert:

Unsere handgezeichnete Tasterskizze
Unsere handgezeichnete Tasterskizze (Große Vorschau)

Als letzten Schliff fügen wir der Kontur der Schaltfläche etwas SVG-Verlaufsmagie hinzu, um ihr ein 3D-Gefühl zu verleihen:

Hinzufügen einer Verlaufsfüllung zum Erstellen von 3D-Feeling
Hinzufügen einer Verlaufsfüllung zum Erstellen von 3D-Feeling (große Vorschau)

Na, bitte! Wir haben die Visuals, jetzt müssen wir sie ins Web bringen.

Von Inkscape zu Web-SVG

Wie ich oben erwähnt habe, lassen sich SVGs ziemlich einfach in HTML einbetten – Sie können einfach den Inhalt der SVG-Datei in Ihr HTML-Dokument einfügen, es in einem Browser öffnen und es wird auf Ihrem Bildschirm gerendert. Sie können es im folgenden CodePen-Beispiel in Aktion sehen:

Sehen Sie sich die Stift-SVG-Drucktaste in HTML von @Uri Shaked an

Sehen Sie sich die Stift-SVG-Drucktaste in HTML von @Uri Shaked an

Aus Inkscape gespeicherte SVG-Dateien enthalten jedoch viel unnötigen Ballast wie die Inkscape-Version und die Fensterposition beim letzten Speichern der Datei. In vielen Fällen gibt es auch leere Elemente, ungenutzte Farbverläufe und Filter, die alle die Dateigröße aufblähen und es schwieriger machen, damit in HTML zu arbeiten.

Glücklicherweise kann Inkscape den größten Teil des Chaos für uns beseitigen. So machen Sie es:

  1. Gehen Sie zum Menü Datei und klicken Sie auf Dokument bereinigen . (Dadurch werden nicht verwendete Definitionen aus Ihrem Dokument entfernt.)
  2. Gehen Sie erneut zu Datei und klicken Sie auf Speichern unter… . Wählen Sie beim Speichern im Dropdown-Menü Dateityp die Option Optimiertes SVG ( *.svg ) aus.
  3. Sie sehen einen Dialog „Optimierte SVG-Ausgabe“ mit drei Registerkarten. Aktivieren Sie alle Optionen, außer „Editordaten beibehalten“, „Definitionen ohne Referenz beibehalten“ und „Manuell erstellte IDs beibehalten…“.
(Große Vorschau)

Wenn Sie all diese Dinge entfernen, wird eine kleinere SVG-Datei erstellt, mit der Sie einfacher arbeiten können. In meinem Fall ging die Datei von 4593 Bytes auf nur noch 2080 Bytes zurück, weniger als die Hälfte der Größe. Bei komplexeren SVG-Dateien kann dies eine enorme Bandbreiteneinsparung bedeuten und die Ladezeit Ihrer Webseite erheblich verbessern.

Das optimierte SVG ist auch viel einfacher zu lesen und zu verstehen. Im folgenden Auszug sollten Sie die beiden Rechtecke, die den Körper der Drucktaste bilden, leicht erkennen können:

 <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"/>

Sie können den Code noch weiter verkürzen, indem Sie beispielsweise die Strichstärke des ersten Rechtecks ​​von 1.0003 auf nur noch 1 ändern. Es macht keinen wesentlichen Unterschied in der Dateigröße, aber es macht den Code leichter lesbar.

Generell ist ein manuelles Übergehen der generierten SVG-Datei immer sinnvoll. In vielen Fällen können Sie leere Gruppen entfernen oder Matrixtransformationen anwenden sowie Gradientenkoordinaten vereinfachen, indem Sie sie vom „verwendeten Benutzerbereich“ (globale Koordinaten) zum „Objektbegrenzungsrahmen“ (relativ zum Objekt) abbilden. Diese Optimierungen sind optional, aber Sie erhalten Code, der einfacher zu verstehen und zu warten ist.

Von diesem Punkt an legen wir Inkscape weg und arbeiten mit der Textdarstellung des SVG-Bildes.

Erstellen einer wiederverwendbaren Webkomponente

Bisher haben wir die Grafiken für unseren Taster, bereit zum Einfügen in unseren Simulator. Wir können die Farbe der Schaltfläche einfach anpassen, indem wir das fill des kleineren Kreises und die Startfarbe des Farbverlaufs des größeren Kreises ändern.

Unser nächstes Ziel ist es, unseren Pushbutton in eine wiederverwendbare Webkomponente zu verwandeln, die durch die Übergabe eines color angepasst werden kann und auf Benutzerinteraktion (Presse-/Freigabeereignisse) reagiert. Wir werden lit-element , eine kleine Bibliothek, die die Erstellung von Webkomponenten vereinfacht.

lit-element zeichnet sich durch die Erstellung kleiner, eigenständiger Komponentenbibliotheken aus. Es basiert auf dem Webkomponenten-Standard, der es ermöglicht, diese Komponenten von jeder Webanwendung zu nutzen, unabhängig vom verwendeten Framework: Angular, React, Vue oder Vanilla JS könnten alle unsere Komponente verwenden.

Das Erstellen von Komponenten in lit-element erfolgt mithilfe einer klassenbasierten Syntax mit einer render() Methode, die den HTML-Code für das Element zurückgibt. Ein bisschen ähnlich wie React, wenn Sie damit vertraut sind. Im Gegensatz zu „react“ verwendet lit-element jedoch Standard-JavaScript-getaggte Template-Literale zum Definieren des Inhalts der Komponente.

So erstellen Sie eine einfache hello-world Komponente:

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

Diese Komponente kann dann überall in Ihrem HTML-Code verwendet werden, indem Sie einfach <hello-world></hello-world> schreiben.

Hinweis : Tatsächlich erfordert unsere Schaltfläche nur ein bisschen mehr Code: Wir müssen eine Eingabeeigenschaft für die Farbe deklarieren, indem wir den @property() decoractor (und mit einem Standardwert von red) verwenden und den SVG-Code in unser render() -Methode, wobei die Farbe der Schaltflächenkappe durch den Wert der color-Eigenschaft ersetzt wird (siehe Beispiel). Die wichtigen Bits befinden sich in Zeile 5, wo wir die Farbeigenschaft definieren: @property() color = 'red'; Auch in Zeile 35 (wo wir diese Eigenschaft verwenden, um die Füllfarbe für den Kreis zu definieren, der die Kappe der Schaltfläche bildet), unter Verwendung der Literal-Syntax der JavaScript-Vorlage, geschrieben als ${color} :

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

Interaktiv machen

Das letzte Puzzleteil wäre, die Schaltfläche interaktiv zu machen. Es gibt zwei Aspekte, die wir berücksichtigen müssen: die visuelle Reaktion auf die Interaktion sowie die programmatische Reaktion auf die Interaktion.

Für den visuellen Teil können wir einfach die Verlaufsfüllung der Schaltflächenkontur umkehren, wodurch die Illusion entsteht, dass die Schaltfläche gedrückt wurde:

Invertieren des Konturverlaufs der Schaltfläche
Konturverlauf der Schaltfläche invertieren (Große Vorschau)

Der Farbverlauf für die Schaltflächenkontur wird durch den folgenden SVG-Code definiert, wobei ${color} wie oben erläutert durch lit-element durch die Farbe der Schaltfläche ersetzt wird:

 <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>

Ein Ansatz für das Aussehen der gedrückten Schaltfläche wäre, einen zweiten Farbverlauf zu definieren, die Reihenfolge der Farben umzukehren und ihn als Füllung des Kreises zu verwenden, wenn die Schaltfläche gedrückt wird. Es gibt jedoch einen netten Trick, mit dem wir denselben Farbverlauf wiederverwenden können: Wir können das SVG-Element mit einer SVG-Transformation um 180 Grad drehen:

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

Das Attribut transform teilt SVG mit, dass wir den Kreis um 180 Grad drehen möchten und dass die Drehung um den Punkt (6, 6) erfolgen soll, der der Mittelpunkt des Kreises ist (definiert durch cx und cy ). SVG-Transformationen wirken sich auch auf die Füllung der Form aus, sodass unser Farbverlauf ebenfalls gedreht wird.

Wir wollen den Farbverlauf nur umkehren, wenn die Schaltfläche gedrückt wird. Anstatt also das transform -Attribut direkt auf dem <circle> -Element hinzuzufügen, wie wir es oben getan haben, werden wir tatsächlich eine CSS-Klasse für dieses Element festlegen und dann davon profitieren der Tatsache, dass SVG-Attribute über CSS gesetzt werden können, wenn auch mit einer etwas anderen Syntax:

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

Diese beiden CSS-Regeln machen genau dasselbe wie die transform , die wir oben hatten – drehen Sie den Kreis um 180 Grad um seinen Mittelpunkt bei (6, 6). Wir möchten, dass diese Regeln nur angewendet werden, wenn die Schaltfläche gedrückt wird, also fügen wir unserem Kreis einen CSS-Klassennamen hinzu:

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

Und jetzt können wir die CSS-Pseudoklasse :active verwenden, um eine Transformation auf die button-contour Element geklickt wird:

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

lit-element ermöglicht es uns, ein Stylesheet an unsere Komponente anzuhängen, indem wir es in einem statischen Getter innerhalb unserer Komponentenklasse deklarieren, indem wir ein getaggtes Vorlagenliteral verwenden:

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

Genau wie die HTML-Vorlage ermöglicht uns diese Syntax, benutzerdefinierte Werte in unseren CSS-Code einzufügen, obwohl wir sie hier nicht benötigen. lit-element kümmert sich auch um die Erstellung von Shadow DOM für unsere Komponente, sodass das CSS nur die Elemente innerhalb unserer Komponente betrifft und nicht auf andere Teile der Anwendung übergeht.

Was ist nun mit dem programmatischen Verhalten der Schaltfläche, wenn sie gedrückt wird? Wir möchten ein Ereignis auslösen, damit die Benutzer unserer Komponente herausfinden können, wann sich der Zustand der Schaltfläche ändert. Eine Möglichkeit, dies zu tun, besteht darin, Mousedown- und Mouseup-Ereignisse auf dem SVG-Element abzuhören und die Ereignisse „Tastendruck“/„Tastenfreigabe“ entsprechend auszulösen. So sieht es mit der lit-element Syntax aus:

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

Dies ist jedoch nicht die beste Lösung, wie wir gleich sehen werden. Aber werfen Sie zuerst einen kurzen Blick auf den Code, den wir bisher erhalten haben:

 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> `; } }
  • Siehe Demo →

Sie können auf jede der Schaltflächen klicken und sehen, wie sie reagieren. Der rote hat sogar einige Ereignis-Listener (definiert in index.html ), wenn Sie also darauf klicken, sollten Sie einige Nachrichten sehen, die in die Konsole geschrieben wurden. Aber warten Sie, was ist, wenn Sie stattdessen die Tastatur verwenden möchten?

Die Komponente zugänglich und mobilfreundlich machen

Hurra! Wir haben eine wiederverwendbare Drucktastenkomponente mit SVG und lit-element erstellt!

Bevor wir unsere Arbeit absegnen, gibt es ein paar Punkte, die wir uns ansehen sollten. Erstens ist die Schaltfläche für Personen, die die Tastatur verwenden, nicht zugänglich. Darüber hinaus ist das Verhalten auf Mobilgeräten inkonsistent – ​​die Schaltflächen erscheinen gedrückt, wenn Sie Ihren Finger darauf halten, aber die JavaScript-Ereignisse werden nicht ausgelöst, wenn Sie Ihren Finger länger als eine Sekunde halten.

Beginnen wir damit, das Tastaturproblem anzugehen. Wir könnten die Schaltfläche über die Tastatur zugänglich machen, indem wir dem svg-Element ein tabindex-Attribut hinzufügen, wodurch es fokussierbar wird. Eine bessere Alternative besteht meiner Meinung nach darin, die Schaltfläche einfach mit einem Standardelement <button> zu umhüllen. Indem wir das Standardelement verwenden, sorgen wir dafür, dass es auch gut mit Screenreadern und anderen Hilfstechnologien zusammenspielt.

Dieser Ansatz hat einen Nachteil, wie Sie unten sehen können:

Unsere hübsche Komponente ist in einem Knopfelement eingeschlossen
Unsere hübsche Komponente, eingeschlossen in einem <button> -Element. (Große Vorschau)

Das <button> -Element verfügt über ein integriertes Styling. Dies könnte leicht behoben werden, indem Sie etwas CSS anwenden, um diese Stile zu entfernen:

 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; }

Beachten Sie, dass wir auch den Selektor ersetzt haben, der das Raster der Schaltflächenkontur umkehrt, indem wir button:active anstelle von svg:active verwenden. Dadurch wird sichergestellt, dass der Button-Pressed-Stil angewendet wird, wenn das eigentliche <button> -Element gedrückt wird, unabhängig vom verwendeten Eingabegerät.

Wir können unsere Komponente sogar bildschirmleserfreundlicher machen, indem wir ein aria-label Attribut hinzufügen, das die Farbe der Schaltfläche enthält:

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

Es gibt noch eine weitere Sache zu bewältigen: die Ereignisse „Tastendruck“ und „Tastenfreigabe“. Idealerweise möchten wir sie basierend auf der CSS :active Pseudoklasse der Schaltfläche auslösen, genau wie wir es im obigen CSS getan haben. Mit anderen Worten, wir möchten das „button-press“-Ereignis auslösen, wenn die Schaltfläche :active wird, und das „button-release“-Ereignis, wenn sie :not(:active) wird.

Aber wie hört man sich eine CSS-Pseudo-Klasse von Javascript an?

Es stellt sich heraus, dass es nicht so einfach ist. Ich habe diese Frage der JavaScript Israel-Community gestellt und schließlich eine Idee ausgegraben, die aus dem endlosen Thread hervorgegangen ist: Verwenden Sie den :active -Selektor, um eine superkurze CSS-Animation auszulösen, und dann kann ich sie mir über JavaScript mit animationstart anhören Veranstaltung.

Ein schnelles CodePen-Experiment bewies, dass dies tatsächlich zuverlässig funktioniert. So sehr mir die Raffinesse dieser Idee gefiel, entschied ich mich für eine andere, einfachere Lösung. Das animationstart -Ereignis ist auf Edge und iOS Safari nicht verfügbar, und das Auslösen einer CSS-Animation nur zum Erkennen der Änderung des Schaltflächenstatus klingt nicht nach der richtigen Vorgehensweise.

Stattdessen fügen wir dem <button> -Element drei Ereignis-Listener-Paare hinzu: mousedown/mouseup für die Maus, touchstart/touchend für Mobilgeräte und keyup/keydown für die Tastatur. Meiner Meinung nach nicht die eleganteste Lösung, aber es funktioniert und funktioniert auf allen Browsern.

 <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()} >

Dabei ist SPACE_KEY eine Konstante, die gleich 32 ist, und up / down sind zwei Klassenmethoden, die die Ereignisse „ button-press “ und „ button-release “ auslösen:

 @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')); } }
  • Den vollständigen Quellcode finden Sie hier.

Wir haben es geschafft!

Es war eine ziemlich lange Reise, die mit der Skizzierung der Anforderungen und dem Zeichnen der Illustration für die Schaltfläche in Inkscape begann, über die Konvertierung unserer SVG-Datei in eine wiederverwendbare Webkomponente mit lit-element und nachdem wir sichergestellt hatten, dass sie zugänglich und mobilfreundlich ist, haben wir endete mit fast 100 Codezeilen einer entzückenden virtuellen Drucktastenkomponente.

Diese Schaltfläche ist nur eine einzelne Komponente in einer Open-Source-Bibliothek virtueller elektronischer Komponenten, die ich baue. Sie sind eingeladen, einen Blick auf den Quellcode zu werfen oder sich das Online-Storybook anzusehen, wo Sie alle verfügbaren Komponenten sehen und mit ihnen interagieren können.

Und schließlich, wenn Sie an Arduino interessiert sind, werfen Sie einen Blick auf den Programmierkurs Simon für Arduino, den ich gerade baue, wo Sie auch den Taster in Aktion sehen können.

Dann bis zum nächsten Mal!