Rich Harris
In diesem Tutorial werden wir die Grundlagen von Svelte kennenlernen und eine einfache Anwendung erstellen. Svelte ist ein modernes Frontend-Framework, das es dir ermöglicht, interaktive Webseiten zu erstellen. Du kannst damit z. B. kleine Spiele oder Webanwendungen entwickeln. In diesem Tutorial werden wir einen »Rubik's Cube Timer« programmieren – also eine Stoppuhr, die die Zeit misst, die du für das Lösen eines Rubik's Cube benötigst.
Bevor du dieses Tutorial beginnst, solltest du dich ein wenig mit HTML und CSS auskennen. Wenn du noch nie mit diesen Technologien gearbeitet hast, empfehlen wir dir, zuerst das Tutorial zu Statischen Webseiten durchzuarbeiten.
Svelte ist ein modernes Frontend-Framework. Es gab vorher schon viele andere Frontend-Frameworks, wie z. B. React (2013), Vue (2014) und Angular (2016). Diese Frameworks haben alle ihre Vor- und Nachteile. Svelte ist ein relativ neues Framework, das 2016 von Rich Harris – einem Journalisten und Softwareentwickler – entwickelt wurde.
Stelle zuerst sicher, dass du keinen Ordner geöffnet hast. Um sicherzugehen, drücke einfach den Shortcut für »Ordner schließen«: StrgK und dann F. Dein Workspace sollte jetzt ungefähr so aussehen:
Klicke dazu auf das Erweiterungs-Symbol in der Seitenleiste oder drücke StrgShiftX. Suche nach der Erweiterung »Svelte for VS Code« und installiere sie.
Um ein neues Svelte-Projekt zu erstellen, öffne ein Terminal und gib folgenden Befehl ein:
npx sv create rubik-timer
Du wirst vermutlich gefragt, ob das sv
-Paket installiert werden soll:
Bestätige mit Enter. Du wirst nun gefragt, welches Template du verwenden möchtest. Wähle das Standard-Template »SvelteKit minimal« aus, indem du Enter drückst:
Bei der nächsten Frage wäre die richtige Antwort »TypeScript«, aber da wir in diesem Tutorial kein TypeScript verwenden, sondern normales JavaScript, wähle hier bitte »No« aus:
Du wirst als nächstes gefragt, ob du schon ein paar Plugins installieren möchtest. Da wir keine Plugins benötigen, kannst du einfach Enter drücken:
Als letztes wirst du gefragt, welchen Paketmanager du verwenden möchtest. Wähle hier »npm« aus, indem du Enter drückst:
Daraufhin musst du noch ein paar Sekunden warten, bis alle Abhängigkeiten installiert sind und das Projekt erstellt wurde. Wenn du die Meldung »Project created« siehst, hat alles geklappt:
/workspace/rubik-timer
löschen und von vorn beginnen.
Öffne nun das Verzeichnis, indem du entweder im Menü »File« / »Open Folder…« auswählst oder einfach StrgK und dann StrgO drückst. Wähle den Ordner rubik-timer
aus:
Bevor wir den Entwicklungs-Server im Workspace starten können, müssen wir noch eine Kleinigkeit anpassen. Öffne die Datei package.json
und ändere den Eintrag für "dev"
von "vite dev"
in "vite dev --host --open"
.
Öffne nun die Datei vite.config.js
und füge einen Eintrag server
hinzu, um den Server für Anfragen aus deinem Workspace zu öffnen:
Du musst also den folgenden Eintrag hinzufügen:
server: { allowedHosts: ['workspace.hackschule.de'] }
Anschließend kannst du den Entwicklungs-Server starten, indem du im Terminal folgenden Befehl eingibst:
npm run dev
Wenn alles geklappt hat, sollte sich deine Webseite automatisch in einem neuen Tab öffnen:
npm run dev
eingibst.
Jetzt, wo wir alles eingerichtet haben, können wir mit dem Programmieren beginnen. Wir werden eine einfache Stoppuhr erstellen, die die Zeit misst, die du für das Lösen eines Rubikwürfels benötigst.
Du kennst dich bereits mit HTML und CSS aus, und jetzt werden wir auch ein wenig JavaScript verwenden, um die Webseite interaktiv zu machen. Bei Svelte werden alle drei Technologien in einer Datei kombiniert, die eine Komponente darstellt. Eine Komponente ist ein wiederverwendbarer Baustein, der eine bestimmte Funktionalität bereitstellt. In unserem Fall wird die Komponente die Stoppuhr darstellen.
Öffne die Datei src/routes/+page.svelte
. Diese Datei enthält den Quelltext für unsere Webseite. Du solltest etwas Code sehen, der ungefähr so aussieht:
<h1>Welcome to SvelteKit</h1> <p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
Du kannst den Code jetzt einfach löschen und durch den folgenden ersetzen:
<script> </script> <div class="main"> <h1>Rubik's Cube Timer</h1> </div> <style> </style>
Du siehst drei Abschnitte:
<script>
: Hier kommt der JavaScript-Code hin, der die Logik der Webseite enthält.<div class="main">
: Hier kommt der HTML-Code hin, der die Struktur der Webseite definiert. Wir haben ein <div>
-Element mit der Klasse main
erstellt, das den Titel der Webseite enthält.<style>
: Hier kommt der CSS-Code hin, der das Aussehen der Seite bestimmt.Wir beginnen mit dem CSS-Code für die Webseite. Füge den folgenden Code in den <style>
-Tag ein, um den Inhalt des .main
-Elements zu zentrieren:
.main { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0 1em; user-select: none; }
Wenn du jetzt die Seite aktualisierst, solltest du den Titel »Rubik's Cube Timer« in der Mitte der Seite sehen. Allerdings stört am rechten Rand noch ein Scrollbalken, den wir mit folgendem CSS-Code in der Datei src/app.html
entfernen können:
html, body { margin: 0; }
Füge den Code in einen <style>
-Tag in der Datei src/app.html
ein (am besten nach dem </body>
). Wenn du die Seite jetzt aktualisierst, sollte der Scrollbalken verschwunden sein.
Um die Stoppuhr zu programmieren, brauchen wir eine Idee, wie sie benutzt werden soll – kümmern wir uns um die »Business-Logik«! Die Stoppuhr kann in verschiedenen Zuständen sein:
Daraus ergibt sich folgender Ablauf:
Um den Zustand auf der Webseite zu speichern, deklarieren wir eine Variable state
im <script>
-Tag:
let state = $state(0); // 0 : idle // 1 : space pressed // 2 : space pressed for 500 ms // 3 : timer running // 4 : timer stopped
Die Variable ist keine normale JavaScript-Variable, sondern eine Svelte-Variable – man erkennt es daran, dass sie in $state(
... )
steht. Das bedeutet, dass wir sie in unseren HTML-Code einbauen können und die Seite automatisch aktualisiert wird, wenn sich der Wert der Variable ändert. Das ist eine der Stärken von Svelte: Es macht es einfach, interaktive Webseiten zu erstellen!
Wir können die Variable state
jetzt in unserem HTML-Code verwenden, um den aktuellen Zustand der Stoppuhr anzuzeigen. Füge den folgenden Code in den <div class="main">
-Tag unterhalb der Überschrift ein:
<p>State = {state}</p>
In deiner Vorschau sollte jetzt der Text »State = 0« erscheinen:
Um zu erkennen, wann die Leertaste gedrückt wird, müssen wir einen Event-Listener hinzufügen. Das sollten wir tun, nachdem die Seite vollständig geladen ist. Dafür brauchen wir den onMount
-Lifecycle-Hook von Svelte. Dieser Hook wird aufgerufen, wenn die Komponente in den DOM eingefügt wird. Füge den folgenden Code ganz oben in den <script>
-Tag ein:
import { onMount } from 'svelte';
Wir können nun den onMount
-Hook verwenden, um den Event-Listener hinzuzufügen. Füge den folgenden Code in das <script>
-Tag ein:
function handleKeyDown() { state = 1; } onMount(() => { document.addEventListener("keydown", (e) => { if (e.code === "Space") { handleKeyDown(); } }); });
Wir definieren eine Funktion handleKeyDown
, die den Zustand auf 1 setzt (wir werden die Logik später noch erweitern). Dann fügen wir im onMount
-Hook einen Event-Listener hinzu, der die Funktion handleKeyDown
aufruft, wenn die Leertaste gedrückt wird.
Wenn du die Seite jetzt aktualisierst und die Leertaste drückst, sollte sich der Zustand auf 1 ändern. Wenn du die Leertaste loslässt, bleibt der Zustand auf 1 stehen. Das liegt daran, dass wir noch keine Logik implementiert haben, um den Zustand zurückzusetzen. Du kannst die Seite aber neu laden, um den Zustand zurückzusetzen.
Wir fügen eine Funktion handleKeyUp
hinzu, die den Zustand zurücksetzt, wenn die Leertaste losgelassen wird. Füge den folgenden Code in das <script>
-Tag ein:
function handleKeyUp() { state = 0; }
Füge außerdem einen weiteren Event-Listener in onMount
hinzu, der die Funktion handleKeyUp
aufruft, wenn die Leertaste losgelassen wird:
document.addEventListener("keyup", (e) => { if (e.code === "Space") { handleKeyUp(); } });
Du solltest jetzt sehen, wie der Zustand auf 0 zurückgesetzt wird, wenn du die Leertaste loslässt.
Damit der Timer auch auf dem Handy funktioniert, wo es keine Leertaste gibt, fügen wir noch einen weiteren Event-Listener im onMount
-Hook hinzu, der auf das Antippen des Bildschirms reagiert:
document.addEventListener("touchstart", () => handleKeyDown()); document.addEventListener("touchend", () => handleKeyUp());
Wir fügen noch noch ein paar Variablen oben im <script>
-Tag hinzu:
let t0 = 0; let timerString = $state("00:00<span class='small'>.00</span>"); let timeoutId = null;
t0
ist die Startzeit des Timers – hiermit berechnen wir später die Zeit, die seit dem Start vergangen ist.timerString
ist der Text, der auf der Webseite angezeigt wird. Wir verwenden hier HTML, um die Zeit mit hunderstel Sekunden anzuzeigen. Die CSS-Klasse small
soll dafür sorgen, dass die hunderstel Sekunden kleiner dargestellt werden – wir definieren sie später. Dieser Variable verwendet auch wieder $state
von Svelte, damit sie im HTML automatisch aktualisiert wird, wenn sich der Wert ändert.timeoutId
ist eine ID, die wir später verwenden werden, um den 500 ms-Timeout abzubrechen, falls die Leertaste vorher losgelassen wird.Im .main
-Element fügen wir jetzt noch eine Anleitung und den eigentlichen Timer hinzu:
<p> Halte die Leertaste gedrückt, bis der Timer grün wird. Wenn du dann los lässt, beginnt die Zeit zu laufen. </p> <p class="timer">{@html timerString}</p>
Im CSS-Abschnitt fügen wir noch ein paar Zeilen hinzu, um den Timer zu formatieren:
.timer { font-size: 300%; font-weight: bold; padding: 0.25em 0.5em; border-radius: 0.2em; background-color: #eeeeec; :global(.small) { font-size: 75%; } :global(&.ready) { transition: background-color 0.3s ease-in; background-color: #73a946; } }
Da Svelte normalerweise allen Code, der nicht verwendet wird, aus Effizienzgründen entfernt, müssen wir CSS-Klassen, die nicht von Anfang an schon im HTML-Code vorhanden sind, mit :global(
...)
kennzeichnen. Das ist hier der Fall, weil die CSS-Klassen ready
und small
erst später durch JavaScript-Funktionen hinzugefügt werden.
Wir brauchen eine Funktion, die die Timeranzeige, also die Variable timerString
, aktualisiert. Füge den folgenden Code in das <script>
-Tag ein:
function updateTimer() { if (state != 3) return; let t1 = Date.now(); let duration = (t1 - t0) / 1000.0; let minutes = `${Math.floor(duration / 60.0)}`; if (minutes.length < 2) minutes = "0" + minutes; let seconds = `${Math.floor(duration % 60)}`; if (seconds.length < 2) seconds = "0" + seconds; let centiseconds = `${Math.floor(duration * 100.0) % 100}`; if (centiseconds.length < 2) centiseconds = "0" + centiseconds; timerString = `${minutes}:${seconds}<span class='small'>.${centiseconds}</span>`; requestAnimationFrame(updateTimer); }
Die Funktion handleKeyDown
muss jetzt noch angepasst werden, damit sie korrekt zwischen den einzelnen Zuständen wechselt:
function handleKeyDown() { if (state === 0) { state = 1; timeoutId = setTimeout(() => { if (state === 1) { state = 2; document.querySelector(".timer")?.classList.add("ready"); } }, 500); } else if (state === 3) { state = 4; } }
Wenn der Zustand 0 ist, wird er auf 1 gesetzt und ein Timeout von 500 ms gestartet. Am Ende dieses Timeouts wird geschaut, ob der Zustand immer noch 1 ist. Wenn ja, wird er auf 2 gesetzt und die Klasse ready
zur Timeranzeige hinzugefügt. Diese Klasse sorgt dafür, dass die Stoppuhr grün wird.
Ist der Zustand jedoch 3, wird er auf 4 gesetzt (die Stoppuhr wird gestoppt).
Die Funktion handleKeyUp
muss ebenfalls angepasst werden:
function handleKeyUp() { if (state === 1) { state = 0; clearTimeout(timeoutId); } else if (state === 2) { state = 3; t0 = Date.now(); requestAnimationFrame(updateTimer); } }
Wenn die Taste losgelassen wird, während wir noch im Zustand 1 sind, wird der Zustand auf 0 zurückgesetzt und der 500 ms-Timeout abgebrochen. Wenn der Zustand 2 ist, wird er auf 3 gesetzt und die aktuelle Zeit in der Variable t0
gespeichert. Außerdem wird mit dem Befehl requestAnimationFrame(updateTimer)
die Funktion updateTimer
einmalig aufgerufen, die die Timeranzeige aktualisiert und dann dafür sorgt, dass sie immer wieder aufgerufen wird.
Der ganzen Code für die Datei src/routes/+page.svelte
sieht jetzt so aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
<script> import { onMount } from "svelte"; let state = $state(0); // 0 : idle // 1 : space pressed // 2 : space pressed for 500 ms // 3 : timer running // 4 : timer stopped let t0 = 0; let timerString = $state("00:00<span class='small'>.00</span>"); let timeoutId = null; function updateTimer() { if (state != 3) return; let t1 = Date.now(); let duration = (t1 - t0) / 1000.0; let minutes = `${Math.floor(duration / 60.0)}`; if (minutes.length < 2) minutes = "0" + minutes; let seconds = `${Math.floor(duration % 60)}`; if (seconds.length < 2) seconds = "0" + seconds; let centiseconds = `${Math.floor(duration * 100.0) % 100}`; if (centiseconds.length < 2) centiseconds = "0" + centiseconds; timerString = `${minutes}:${seconds}<span class='small'>.${centiseconds}</span>`; requestAnimationFrame(updateTimer); } function handleKeyDown() { if (state === 0) { state = 1; timeoutId = setTimeout(() => { if (state === 1) { state = 2; document.querySelector(".timer")?.classList.add("ready"); } }, 500); } else if (state === 3) { state = 4; } } function handleKeyUp() { if (state === 1) { state = 0; clearTimeout(timeoutId); } else if (state === 2) { state = 3; t0 = Date.now(); requestAnimationFrame(updateTimer); } } onMount(() => { document.addEventListener("keydown", (e) => { if (e.code === "Space") { handleKeyDown(); } }); document.addEventListener("keyup", (e) => { if (e.code === "Space") { handleKeyUp(); } }); document.addEventListener("touchstart", () => handleKeyDown()); document.addEventListener("touchend", () => handleKeyUp()); }); </script> <div class="main"> <h1>Rubik's Cube Timer</h1> <p>State = {state}</p> <p> Halte die Leertaste gedrückt, bis der Timer grün wird. Wenn du dann los lässt, beginnt die Zeit zu laufen. </p> <p class="timer">{@html timerString}</p> </div> <style> .main { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0 1em; user-select: none; } .timer { font-size: 300%; font-weight: bold; padding: 0.25em 0.5em; border-radius: 0.2em; background-color: #eeeeec; :global(.small) { font-size: 75%; } :global(&.ready) { transition: background-color 0.3s ease-in; background-color: #73a946; } } </style> |
Deine Seite sollte jetzt so aussehen:
Jetzt, wo die Stoppuhr funktioniert, können wir uns um das Styling kümmern. Wir haben bereits ein paar CSS-Regeln hinzugefügt, aber es gibt noch ein paar Dinge, die wir verbessern können.
Wir können die Schriftart der Webseite ändern, um sie ansprechender zu gestalten. Dazu verwenden wir Google Fonts. Füge den folgenden Code in den <head>
-Tag der Datei src/app.html
ein:
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,700;1,400;1,700&family=Quicksand:wght@300..700&display=swap" rel="stylesheet">
Ändere außerdem den CSS-Code in der Datei src/routes/+page.svelte
, so dass die Schriftarten verwendet werden:
.main { font-family: Quicksand; } .timer { font-family: "IBM Plex Mono"; }
Du solltest jetzt sehen, dass die Schriftart der Webseite geändert wurde. Wenn du die Seite aktualisierst, sollte sie jetzt so aussehen:
Bootstrap ist ein beliebtes CSS-Framework, das dir hilft, deine Webseite schnell und einfach zu gestalten. Du kannst es einbinden, indem du den folgenden Code in den <head>
-Tag der Datei src/app.html
einfügst:
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
Jetzt kannst du Bootstrap-Klassen verwenden, um deine Webseite zu gestalten. Unter anderem stellt Bootstrap Klassen zum Styling von Buttons zur Verfügung, die wir für unseren Reset-Button verwenden können. Füge den folgenden Code in den <div class="main">
-Tag ein, um einen Reset-Button hinzuzufügen:
<button id="bu_reset" class="btn btn-lg {state < 3 ? 'btn-outline-secondary' : 'btn-warning'}" disabled={state < 3 ? "disabled" : ""} on:click={resetTimer}> Reset </button>
Wir definieren hier einen Button mit der ID bu_reset
, der die Klasse btn
von Bootstrap hat. Außerdem verwenden wir die Klasse btn-outline-secondary
(graue Umrandung), wenn state
kleiner als 3 ist, und ansonsten die Klasse btn-warning
(gelbe Farbe). Außerdem wird der Button deaktiviert, wenn state
kleiner als 3 ist. Wenn der Button geklickt wird, wird die Funktion resetTimer
aufgerufen.
Wir müssen jetzt noch eine Funktion resetTimer
hinzufügen, die den Timer zurücksetzt. Füge den folgenden Code in das <script>
-Tag ein (z. B. hinter der Funktion handleKeyUp
):
function resetTimer() { if (state > 2) { state = 0; t0 = 0; timerString = "00:00<span class='small'>.00</span>"; document.querySelector(".timer")?.classList.remove("ready"); } }
Auf dem Reset-Button fehlt noch ein Icon. Wir verwenden dafür Iconify, eine Sammlung von Icons, die du ganz einfach in deine Webseite einfügen kannst. Um die Icons zu verwenden, müssen wir das Iconify-Paket installieren. Das geht ganz einfach mit dem folgenden Befehl:
npm install @iconify/svelte
Wenn du das Paket installiert hast, kannst du die Icons ganz einfach in deine Webseite einfügen. Füge den folgenden Code in den <script>
-Tag ein:
import Icon from '@iconify/svelte';
Füge dann den folgenden Code in den <button>
-Tag ein (genau vor dem Label »Reset«), um das Icon anzuzeigen:
<Icon icon="material-symbols:device-reset-rounded" class="icon" />
Das Icon wird jetzt in dem Button angezeigt. Du kannst die Größe des Icons ändern, indem du die CSS-Klasse icon
anpasst. Füge den folgenden Code in den <style>
-Tag ein:
:global(.btn .icon) { margin-right: 0.25em; padding-bottom: 0.1em; transform: scale(1.3); }
Wenn du die Seite jetzt aktualisierst, solltest du den Reset-Button mit dem Icon sehen:
Du kannst dir hier auch andere Icons aussuchen: https://icon-sets.iconify.design/. Du kannst die Icons ganz einfach in deine Webseite einfügen, indem du den Namen des Icons in den <Icon>
-Tag einfügst. Zum Beispiel:
<Icon icon="mdi:home" class="icon" />
Um deine Webseite online zu stellen, bietet es sich an, sie als statische Webseite zu exportieren. Das bedeutet, dass die Webseite in eine HTML-Datei umgewandelt wird, die du dann auf einem Webserver hosten kannst. Gegenüber dynamischen Webseiten, die bei jedem Aufruf neu generiert werden, sind statische Webseiten schneller und benötigen weniger Ressourcen. Außerdem verzichtest du so auf eine relativ große Angriffsfläche, die ein Backend mit sich bringen kann.
Um deine Webseite als statische Webseite zu exportieren, müssen wir erst einen statischen Adapter für Svelte installieren. Das geht ganz einfach mit dem folgenden Befehl:
npm install -D @sveltejs/adapter-static
Anschließend musst du den Adapter in der Datei svelte.config.js
aktivieren. Öffne die Datei und ändere adapter-auto
in adapter-static
:
Wir müssen Svelte jetzt noch mitteilen, dass wir die Webseite als statische Webseite exportieren möchten. Erstelle dazu eine neue Datei src/routes/+layout.js
und füge den folgenden Code ein:
export const prerender = true;
Anschließend kannst du den statischen Build erstellen, indem du im Terminal folgenden Befehl eingibst:
npm run build
Wenn alles geklappt hat, solltest du ein neues Verzeichnis build
in deinem Verzeichnis sehen, das alle Dateien enthält, die du für deine Webseite benötigst.
build
-Verzeichnis öffnest und den Live-Server startest. Sie sollte genauso aussehen und funktionieren wie die Seite, die du mit dem Entwicklungs-Server siehst.
Du kannst deine Webseite ganz einfach veröffentlichen, indem du sie auf einen Webserver hochlädst. Es gibt viele kostenlose Hosting-Anbieter, die dir helfen können, deine Webseite online zu stellen. Du kannst deine Seite aber auch gern kostenlos unter einer Subdomain von hackschule.de veröffentlichen (also z. B. meineseite.hackschule.de). Wenn du das möchtest, schreib einfach eine E-Mail an specht@gymnasiumsteglitz.de. Eine Anleitung, wie du auf deinen Webspace zugreifen kannst, findest du hier.