Willkommen
Dieser Kurs ermöglicht dir einen Einstieg in die Webentwicklung mit Html, CSS und JavaScript. Anhand einer Blog Page von Galaxus wirst du nach und nach vieles davon nachbauen und sehen, was es teilweise braucht, um ein schönes Layout hinzukriegen oder wie ein Button zu Funktionen verholfen wird.
Viel Spass beim Lernen und ausprobieren.
| Was | Link |
|---|---|
| HTML, CSS, Javascript | |
| Nachschlage Werk für html, css, javascript | SELFHTML Wiki |
| Effektive "Bibel" von der Mozilla Foundation, was bei html, css, javascript grundsätzlich gilt / funktionieren sollte. Inklusive lern Beispiele | MDN Web Docs - HTML |
| Nachschlagewerk zu CSS Grid | CSS Grid Complete Guide |
| Nachschlagewerk zu CSS Flexbox | CSS Flexbox Guide |
| CSS Variablen | W3Schools CSS Variables |
| CSS Nesting | W3Schools CSS Nesting |
| CSS Methodologies ➡️ welche Architekturen für CSS gibt es | CSS Methodologies Guide |
| Was kann Vs Code. Bspw. HTML | VS Code HTML Features |
Tools | |
| Zum nachprüfen, auf welche API's heute ein Browser zugreifen kann bzw. darf; äusserst hilfreich, wenn man anstelle einer App überlegt, eine Webseite zu bauen | What Web Can Do Today |
| Zum nachprüfen, was von welchen Browser unterstützt wird | Can I Use |
| Vs Code | Visual Studio Code |
| Node Js | Node.js Download |
| Debug mit Chrome | Chrome DevTools JavaScript |
| Debug mit VSCode | VS Code Debugging |
Kurs relevante Links | |
| Github | GitHub Repository |
| End-Resultate | Web Engineering Results |
Tutorials
| Was | Link |
|---|---|
| Html, CSS, Javascript | freeCodeCamp Full Stack Developer |
| Html | W3Schools HTML Tutorial |
| CSS | W3Schools CSS Tutorial |
| JavaScript | W3Schools JavaScript Tutorial |
| TypeScript | TypeScript Nachschlagewerk |
| CSS Grid | CSS Grid Garden |
| CSS Flexbox | Flexbox Froggy |
| Verschiedene | Scrimba Interactive Tutorials |
| Angular | Angular Official Tutorial |
| React | React Tutorial App |
Video Aufzeichnungen
Die Links funktionieren nur bei entsprechender Berechtigung.
| Datum Kurstag | Link |
|---|---|
| 19. August | Kurstag Setup |
| 4. September | Erster HTML Kurstag |
| 16. September | Zweiter HTML Kurstag |
| 30. September | Teil 1 CSS |
| 7. Oktober | Teil 2 CSS |
| 14. Oktober | Teil 3 CSS |
| 21. Oktober | JavaScript Einstieg |
Setup
Erforderliche Applikationen
Folgende Applikationen musst du auf deinem Gerät installiert haben:
- Visual Studio Code - Code-Editor
- NodeJS - JavaScript Runtime
- Chrome - Browser für Entwicklung
Für jede Applikation findest du eine detaillierte Beschreibung in diesem Setup-Bereich.
Visual Studio Code
Visual Studio Code wird das wichtigste während dem Kurs sein. Damit erstellst du den Code für die Webseite. Du wirst rasch sehen, wie viel Unterstützung dir VS Code dabei bietet.
Download
- Besuche die Seite Vs Code Download
- Wähle die Version deines Betriebssystems und klicke auf den großen Button. Dieser wählt in der Regel die beste Option für deine Arbeitsumgebung.

Settings
Öffne die Einstellungen im VS Code. Passe folgendes an:
| Suchbegriff | Einstellung |
|---|---|
| editor.linkedEditing | Aktiviere "linked editing" ![]() |
| save | Überprüfe die Einstellungen zu "save". Mindestens "Auto Save" → onFocusChange aktivieren ergibt Sinn. Zudem kannst du auch einstellen, dass z.B. dein File automatisch formatiert wird ![]() |
| Liveserver (zuerst Plugin installieren) | Klicke dann auf und erweitere das JSON so, dass es den Wert fürs Debug Attach auf true hat! ![]() |
Plugins
Sobald du Visual Studio Code (VS Code) installiert hast, öffne es. Damit das Leben mit VS Code noch angenehmer wird, kannst du noch ein paar Plugins installieren. Dafür gehst du in deinem VS Code auf den Bereich "Erweiterungen" und gibst den Namen oben ein. Danach klickst du jeweils auf "Installieren". Die Version kann sich unter Umständen erhöht haben, wenn du die Erweiterungen installierst. Die Angabe unten ist die Mindest-Version.

Erforderliche Plugins
| Plugin | Version | Beschreibung |
|---|---|---|
| Highlight Matching Tag | 3.x | Färbt Klammern / Code die zusammenpassen ein |
| Path Intellisense | 16.x | Vereinfacht die Angabe von Pfaden |
| Prettier - Code Formatter | 58.x | Kann den Code automatisch formatieren. Hilft, um eine bessere Übersicht zu haben |
| Live Server | 64.x | Web Server, damit die Webseite "richtig" ausgeführt werden kann |
Optionale Plugins
Für den Kurs nicht zwingend nötig, können aber allenfalls zusätzlich unterstützen:
| Plugin | Version | Beschreibung |
|---|---|---|
| German Language Pack for Visual Studio Code | 4.x | Damit kannst du VS Code in Deutsch nutzen |
NodeJS
NodeJS verwenden wir nur im Hintergrund. Dieses Tool ist dafür zuständig, dass dein Computer JavaScript ausführen kann. Da wir eine Webseite programmieren, welche auf JavaScript aufbaut, ist dieses Tool essentiell.
Download und Installation
Wähle auf der Seite Node Download LTS und danach die 64-Bit Version von Windows bzw. Mac.

Bei den Installationsschritten kannst du überall den Standard belassen und dich bis zum Abschluss durchklicken.
Alternative Installation ohne Admin-Rechte
Wenn du keine Admin-Rechte hast, kannst du diese Anleitung verwenden: Install without Admin Rights
Online-Alternativen
Ansonsten können folgende Online-Tools verwendet werden:
- Browser (JavaScript Console)
- StackBlitz - Online-Entwicklungsumgebung
- Programiz Online Compiler - online compiler
- Andere beliebige Node.js Online-Tools
Chrome
Falls du den Chrome Browser noch nicht installiert hast, ist es jetzt an der Zeit. Die Entwicklungstools von Chrome bieten einiges, was andere Browser leider nicht mitbringen. Du kannst den Browser nach dem Kurs auch wieder löschen und ihn vor allem zum Fehler suchen einsetzen.
Download und Installation
- Öffne download Chrome
- Der dort angezeigte Download-Knopf sollte eigentlich immer der korrekte für dein Betriebssystem sein
- Die Installationsschritte sind selbsterklärend - befolge sie bis zum Abschluss der Installation
🌐 HTML Einstieg
Grundlagen lernen
Am einfachsten lernst du die HTML-Grundlagen im Tutorial-Kurs von FreeCodeCamp. Mit dem Abschluss des HTML-Teils solltest du die wichtigsten Tags kennen, die wir auch in unserem Beispiel nutzen werden.
Erste HTML-Seite erstellen
Schritt 1: Projekt-Setup
- Visual Studio Code öffnen
- "Ordner öffnen" wählen

- Passenden Ordner anlegen (z.B. "myFirstWebpage")
- Öffnen des Ordners bestätigen
Schritt 2: HTML-Datei erstellen
-
Neues File anlegen:
index.html -
Emmet-Abkürzung verwenden:
- Schreibe
!gefolgt vonTAB - Dies führt die Emmet Abbreviation aus
- Schreibe
-
Grundgerüst sollte automatisch erstellt werden:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
Schritt 3: LiveServer starten
- Rechtsklick auf
index.html - "Open with LiveServer" wählen

💡 Hinweis: Falls der Button nicht verfügbar ist, überprüfe dein Setup und installiere die LiveServer Extension (siehe Abschnitt "Setup")
Schritt 4: Browser-Ansicht
- Automatisches Öffnen im Browser-Fenster
- Standard-Port: 5500
- Live-Reload: Änderungen werden sofort angezeigt
LiveServer konfigurieren
Custom Browser einstellen
- Einstellungen öffnen
- Nach "liveserver" suchen
- Custom Browser einstellen

Erweiterte Konfiguration
Für weitere Einstellungen kannst du alles in der settings.json bearbeiten:
{
"liveServer.settings.port": 5500,
"liveServer.settings.CustomBrowser": "chrome",
"liveServer.settings.donotShowInfoMsg": true
}
Nächste Schritte
Nach dem erfolgreichen Setup kannst du mit dem Blog Page Tutorial fortfahren und deine erste komplette Webseite erstellen!
✅ Was du jetzt kannst:
- HTML-Grundgerüst erstellen
- LiveServer verwenden
- Live-Reload für Entwicklung nutzen
- VS Code für Web-Entwicklung konfigurieren
📄 Blog Page erstellen
Projekt-Ziel
Wir bauen eine Blog Page nach dem Vorbild von Galaxus Beispiel-Artikel.
Die Page enthält:
- Video/Bild als Slide
- Fazit-Bereiche
- Tabellen und Bilder
- Produkt-Kacheln
- Empfohlene Blog-Pages
- Like- und Kommentar-Funktionen
Entwicklungsansatz
Struktur vor Design: Wir fokussieren uns zunächst auf das HTML-Grundgerüst. Das Design kommt später mit CSS.
⚠️ Hinweis: Ignoriere die linke und rechte Spalte. Wir konzentrieren uns ausschließlich auf die Blog Page selbst.
Mindest-Anforderungen
✅ Kern-Elemente (Must-have)
- Video oder Bild als Slide (freie Tag-Wahl)
- Rubrik
- Titel im H1-Format
- Lead-Text
- Haupttext
- Weitere Überschriften im H2-Format (max. ein H1 pro Page!)
- Weitere Bilder
- Gefällt mir Button
- Empfehlungen mit weiteren Blog Pages
🌟 Erweiterte Elemente (Nice-to-have)
Wenn Zeit vorhanden:
- Autor:innen-Bereich
- Tabelle
- Fazit-Box
- Produkt-Kachel
- Autor:innen-Box mit Folgen-Button
- Thema mit Folgen-Button
🚫 Verzicht: Community-Bereich (zu komplex für Einstieg)
HTML-Grundgerüst erstellen
Schritt 1: Projekt-Setup
- VS Code öffnen
- Ordner für Übungen anlegen
- GitHub Repository für Referenz: webEngineerDgEditors
Schritt 2: Basis-Datei erstellen
index.htmlanlegen- Grundgerüst mit
!+ Tab erstellen - LiveServer starten für sofortige Änderungsanzeige
- Titel definieren
Schritt 3: Body-Struktur aufbauen
A. Media-Bereich
<!-- Video/Image als Slide -->
<iframe
src="YOUTUBE_EMBED_URL"
title="Video Titel">
</iframe>
<!-- ODER -->
<img src="bild.jpg" alt="Beschreibung">
B. Content-Header
<!-- Rubrik -->
<p class="category">Kategorie</p>
<!-- Haupttitel -->
<h1>Artikel-Titel</h1>
<!-- Lead-Text -->
<h2>Lead-Text oder Untertitel</h2>
C. Hauptinhalt
<!-- Text-Abschnitte -->
<p>Erster Textabschnitt...</p>
<!-- Gruppierte Inhalte -->
<section>
<h3>Zwischenüberschrift</h3>
<p>Zugehöriger Text...</p>
<img src="bild.jpg" alt="Beschreibung">
</section>
D. Interaktions-Elemente
<!-- Gefällt mir Button -->
<button>❤️ Gefällt mir</button>
E. Empfehlungen
<!-- Empfohlene Artikel -->
<section>
<h2>Diese Beiträge könnten dich auch interessieren</h2>
<!-- Struktur für empfohlene Pages -->
</section>
Content-Strategien
Text-Optionen
| Methode | Beschreibung | Verwendung |
|---|---|---|
| Lorem Ipsum | lorem + Tab in VS Code | Platzhalter-Text |
| Original-Content | Von Beispiel-Seite kopieren | Realitätsnaher Content |
| Eigener Content | Selbst geschriebene Texte | Personalisierte Inhalte |
Semantische HTML-Struktur
Verwende logische Gruppierungen:
<!-- Beispiel: Gruppierte Inhalte -->
<article>
<section class="intro">
<h1>Titel</h1>
<p>Lead</p>
</section>
<section class="content">
<h2>Abschnitt</h2>
<p>Text</p>
</section>
<section class="recommendations">
<h2>Empfehlungen</h2>
<!-- Empfohlene Artikel -->
</section>
</article>
Best Practices
HTML-Struktur
- Einen H1 pro Page
- Logische Heading-Hierarchie (H1 → H2 → H3)
- Semantische Tags verwenden (
article,section,aside) - Alt-Attribute für alle Bilder
Media-Integration
YouTube-Video einbetten:
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/VIDEO_ID"
title="Video Titel"
frameborder="0"
allowfullscreen>
</iframe>
Responsive Bilder:
<img
src="bild.jpg"
alt="Beschreibende Bildbeschreibung"
width="100%">
Entwicklungsworkflow
Schritt-für-Schritt-Checklist
- ✅ HTML-Grundgerüst erstellen
- ✅ LiveServer starten
- ✅ Media-Bereich implementieren
- ✅ Content-Header aufbauen
- ✅ Hauptinhalt strukturieren
- ✅ Interaktions-Elemente hinzufügen
- ✅ Empfehlungen-Bereich erstellen
- ✅ Erweiterte Elemente (optional)
Iterativer Ansatz
- Basis-Version mit Mindest-Elementen
- Schritt-für-Schritt-Erweiterung
- Kontinuierliche Browser-Tests
- Spätere CSS-Integration
🚀 Nächste Schritte
Nach Fertigstellung der HTML-Struktur:
- CSS-Styling hinzufügen
- Responsive Design implementieren
- JavaScript-Interaktivität einbauen
- Performance-Optimierung
Ziel erreicht: Solide HTML-Basis für eine professionelle Blog Page! 🎉
🎨 CSS Einstieg
💡 Was ist CSS?
CSS steht für Cascading Style Sheets und bildet die Grundlage des Designs für jede Webseite.
🔍 Wichtig: Sogar wenn du kein eigenes CSS definiert hast, gibt es ein CSS! Jeder Browser definiert dies über das "User Agent Stylesheet".
🔧 CSS im Browser untersuchen
📋 Schritt-für-Schritt-Anleitung
1. Beispiel-Seite öffnen
- Galaxus Artikel in Chrome öffnen
- Rechtsklick auf einen Bereich der Seite
- "Untersuchen" auswählen (oder F12 drücken)

2. Entwicklertools verstehen
Das Entwickler-Werkzeug wird auf der rechten Seite angezeigt. Die Position lässt sich umschalten.

3. Auswahl-Werkzeug aktivieren
- Auswahl-Werkzeug in der Toolbar aktivieren
- Ermöglicht das direkte Anklicken von Elementen

4. Element inspizieren
- Überschrift oder anderes Element anklicken
- CSS-Eigenschaften werden rechts unter "Styles" angezeigt

🏗️ CSS-Hierarchie verstehen
📊 Kaskadierung (Cascading)
Von unten nach oben gelesen:
| Priorität | Quelle | Beschreibung |
|---|---|---|
| Niedrigste | User Agent Stylesheet | Browser-Standard CSS |
| Mittlere | Website CSS | Entwickler-definierte Styles |
| Höchste | Spezifische Selektoren | Überschreibende Definitionen |
🔍 Was du in den DevTools siehst:
- 📋 User-Agent-Stylesheet: Browser-Standard (unterste Ebene)
- 🎯 Hierachische Definitionen: Von unten nach oben
- ✂️ Durchgestrichene Regeln: "Verlierende" CSS-Definitionen
- 🏆 Gewinnende Definition: Letzte, spezifischste Regel
- 🤖 Framework-Klassen: Z.B.
.yDkbjfY3(automatisch generiert)
💡 Beispiel-CSS-Eigenschaft
margin-bottom: 16px; /* 16 Pixel Abstand unterhalb des Elements */
📚 CSS lernen
🎯 Empfohlene Lernressourcen
🚀 FreeCodeCamp Tutorial
Link: Full Stack Developer Kurs
Vorgehen:
- CSS-Sektion auswählen
- Computer Basics überspringen (falls bereits bekannt)
- Responsive Web Design für Fortgeschrittene
📖 Lernweg-Struktur
1. CSS Basics
├── Selektoren
├── Eigenschaften
└── Werte
2. Layout-Konzepte
├── Box Model
├── Flexbox
└── CSS Grid
3. Responsive Design
├── Media Queries
├── Breakpoints
└── Mobile First
🎨 Praktische Anwendung
🎯 Nächster Schritt: Blog-Seite stylen
Nach dem Tutorial-Durchlauf:
- ✅ HTML-Grundgerüst (bereits vorhanden)
- 🎨 CSS-Design hinzufügen
- 📱 Responsive machen
- ✨ Interaktivität erweitern
🔥 Ziel: Die Blog-Seite von einem reinen HTML-Gerüst zu einer ansprechend gestylten Webseite verwandeln!
💡 Best Practices
✅ CSS-Grundregeln
- 📝 Kommentare für komplexe Bereiche
- 🏗️ Logische Struktur in CSS-Dateien
- 🎯 Spezifische Selektoren verwenden
- 🔄 Wiederverwendbare Klassen erstellen
🔧 Browser-DevTools nutzen
- 🔍 Live-Editing für schnelle Tests
- 📊 Computed Styles für finale Werte
- 🎯 Element-Inspektion für Debugging
- 📱 Device-Simulation für Responsive Tests
🚀 Workflow-Empfehlung
📋 Entwicklungsschritte
- 🖼️ Design analysieren (Galaxus-Beispiel)
- 🏗️ HTML-Struktur überprüfen
- 🎨 CSS schrittweise hinzufügen
- 🔄 Browser-Tests kontinuierlich
- 📱 Responsive optimieren
Bereit für kreatives CSS-Design! 🎉
📱 Responsive Web Design
🕰️ Die Evolution des Web Designs
📊 2009: Die einfache Zeit
Im Jahr 2009 war noch alles viel einfacher. Die meisten User nutzten eine Bildauflösung von 1024x768 Pixel - dem damaligen Desktop-Standard.

Alle Auflösungen waren im Desktop-Segment angesiedelt. Die Welt des Web-Engineers war einfach:
- Eine Seite für alle
<table>Tags für Layout- Feste Breiten funktionierten perfekt
SBB Seite

Apple Seite

📈 2014: Der Mobile Wandel

Deutlich kleinere Auflösungen dominieren das Chart:
- 📱 Smartphones an der Spitze
- "Others" deutlich gestiegen
- 💥 Paradigmenwechsel: Responsive Design wird Pflicht
🚨 Realität: Smartphones haben der Webentwicklung einen Dämpfer verpasst. Plötzlich müssen Seiten auch auf relativ kleinen Auflösungen gut aussehen.
📱 Mobile First & Responsive Design
🍎 Das iPhone-Problem (2008)
Ursprüngliche Lösung: Webseiten wurden "gezoomed" dargestellt
- Browser verkleinerte Desktop-Darstellung
- Zoom-Geste ermöglichte Navigation
- ❌ Problem: Nicht zielführend für positive User Experience
🔧 Die Viewport-Lösung
Emmet-Shortcut ! erstellt automatisch:
<meta name="viewport" content="width=device-width, initial-scale=1" />
Was passiert:
- Browser zeigt Webseite 1:1 dar (keine Verkleinerung)
- 400px Button = volle Breite auf 400px-Screen
- Verantwortung liegt bei der Entwicklerin
📖 Mehr Infos: W3Schools Viewport Guide
🏗️ Mobile First Strategie
📋 Konzept-Übersicht
Mobile First = Design beginnt mit kleinster Auflösung
📱 320px (Mobile) → 📲 768px (Tablet) → 🖥️ 1200px (Desktop)
🎯 Breakpoint-System
Breakpoint = Bestimmte Auflösung, wo sich das Design anpassen muss
Beispiel-Szenario:
- Button bis 400px:
width: 100%✅ - Button ab 450px: Anpassung nötig ❌
- Lösung: Breakpoint bei
400pxdefinieren
📊 Bootstrap Breakpoints (Beispiel)

Mobile First erkennbar:
- Erster Breakpoint:
min-width: 576px - Bis dahin: "Kleinster Screen" als Basis
- Alle weiteren:
min-widthfür schrittweise Erweiterung
🛠️ Praktische Umsetzung
📁 Projekt-Setup
Schritt 1: Ordner-Struktur
- Neuen Ordner erstellen
- VS Code öffnen
- Files erstellen:
index.html+main.css

Schritt 2: Grid-System vorbereiten
W3Schools Referenz: CSS RWD Intro
12-Spalten-System (bewährter Standard):
- Flexible Aufteilung für verschiedene Layouts
- Prozentuale Breiten für responsive Verhalten
📂 Grid CSS erstellen
Ordner-Struktur:
project/
├── index.html
├── main.css
└── styleguide/
└── grid.css
Grid-Klassen definieren: (grid.css)
.col-1 {
width: 8.33%;
}
.col-2 {
width: 16.66%;
}
.col-3 {
width: 25%;
}
.col-4 {
width: 33.33%;
}
.col-5 {
width: 41.66%;
}
.col-6 {
width: 50%;
}
.col-7 {
width: 58.33%;
}
.col-8 {
width: 66.66%;
}
.col-9 {
width: 75%;
}
.col-10 {
width: 83.33%;
}
.col-11 {
width: 91.66%;
}
.col-12 {
width: 100%;
}
🔧 HTML-Grundgerüst
index.html Body:
<body>
<h1>Responsive is cool</h1>
<div class="col-3">first column</div>
<div class="col-3">second column</div>
<div class="col-3">third column</div>
<div class="col-3">fourth column</div>
</body>
Box-Sizing hinzufügen (main.css):
* {
box-sizing: border-box;
}
Grid-Styling erweitern (grid.css):
[class*="col-"] {
float: left;
padding: 15px;
border: 1px solid red;
}
🔗 CSS verknüpfen
HTML Head erweitern:
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="main.css" />
</head>
CSS Import (main.css):
@import "styleguide/grid.css";
Jetzt sollte deine Page wie folgt aussehen.

📐 Row-System implementieren
Was noch fehlt ist die Möglichkeit, einzelne Zeilen zu definieren. Wir möchten sicherstellen, dass sich die Verteilung nur auf einen bestimmten Bereich bezieht. Dafür benötigen wir noch folgende css Anweisung in unserem grid.css file
Row-CSS hinzufügen (grid.css):
.row::after {
content: "";
clear: both;
display: table;
}
HTML mit Rows strukturieren:
<body>
<h1>Responsive is cool</h1>
<div class="row">
<div class="col-3">first column</div>
<div class="col-3">second column</div>
<div class="col-3">third column</div>
<div class="col-3">fourth column</div>
</div>
</body>
Spiele nun etwas mit der Breite des Bildschirms. Du wirst nun allenfalls feststellen, dass nicht alle Breiten gut aussehen. Je Breiter der Bildschirm, desto mehr unnötige Abstände produzieren wir. Genau das wollen wir nun verbessern, indem wir im Zusammenspiel mit den MediaQueries unser Grid erst richtig erweitern.
Da wir mobile-first designen, kümmern wir uns um die Namens Zusätze erst ab dem ersten Breakpoint. Also in unserem Beispiel der Breakpoint mit dem Namen sm für Small
📱 Responsive Breakpoints
🎯 Media Queries implementieren
Mobile First Basis-Grid:
/* Mobile (Standard - kein Breakpoint nötig) */
.col-1 {
width: 8.33%;
}
.col-2 {
width: 16.66%;
}
/* ... bis col-12 */
Small Devices (576px+):
@media (min-width: 576px) {
.col-sm-1 {
width: 8.33%;
}
.col-sm-2 {
width: 16.66%;
}
.col-sm-3 {
width: 25%;
}
.col-sm-4 {
width: 33.33%;
}
/* ... bis col-sm-12 */
}
💡 Responsive HTML-Beispiel
Nun hast du die Möglichkeit, das Layout für jeden Breakpoint zu definieren. Mache bspw. mal col-12 im mobile, für sm danach col-sm-3 und siehe wie sich das Layout nun verhält.
<div class="row">
<div class="col-12 col-sm-3">first column</div>
<div class="col-12 col-sm-3">second column</div>
<div class="col-12 col-sm-3">third column</div>
<div class="col-12 col-sm-3">fourth column</div>
</div>
Verhalten:
- Mobile: 4 Zeilen (je 100% Breite)
- ab 576px: 4 Spalten (je 25% Breite)
🤔 Library vs. Custom Solution
Nun hast du gesehen, dass es nicht unbedingt Magie ist, ein Grid selber zu programmieren. Damit alles funktioniert, fehlen trotzdem noch einige CSS Klassen, die noch definiert werden müssen. Daher stellt sich doch gleich die Frage, wieso nicht von Anfang an Bootstrap oder andere CSS Bibliotheken einsetzen? Wenn dein Projekt keine speziellen Anforderungen hat, du mit grundsätzlich 12 Spalten problemlos dein Design umsetzen kannst, dann kannst du gut und gerne eine Bibliothek wie Bootstrap einsetzen. Trotzdem muss man sich immer bewusst sein, dass der Einsatz einer solchen Bibliothek auch erfordert, dass man sie korrekt versteht und entsprechend korrekt einsetzt. Du musst die Klassen verstehen und wissen, welche für was eingesetzt wird. Ein einfaches Beispiel mit Bootstrap sieht wie folgt aus
<div class="container">
<div class="row">
<div class="col-sm">One of three columns</div>
<div class="col-sm">One of three columns</div>
<div class="col-sm">One of three columns</div>
</div>
</div>
⚖️ Entscheidungskriterien
| Aspekt | Bootstrap | Custom Grid |
|---|---|---|
| ⚡ Setup-Zeit | Schnell | Länger |
| 🎯 Flexibilität | Begrenzt | Vollständig |
| 📚 Lernaufwand | Framework lernen | CSS verstehen |
| 🔧 Anpassungen | Workarounds | Direkte Kontrolle |
| 📦 Bundle-Size | Größer | Minimal |
✅ Bootstrap verwenden, wenn:
- 🚀 Schnelle Prototypen erstellen
- 12-Spalten-System ausreicht
- Standard-Designs umsetzen
- Team bereits Bootstrap-Erfahrung hat
✅ Custom Grid verwenden, wenn:
- 🎨 Unique Designs entwickeln
- Volle Kontrolle benötigt
- Performance kritisch ist
- Lernziele CSS-Verständnis beinhalten
🚀 The Modern Way: CSS Grid
🌟 CSS Grid Vorteile (seit 2016/2017)
Warum CSS Grid?
- 🏗️ 2D-Layout (Zeilen + Spalten)
- 🎯 Intuitive Syntax
- 📱 Native Browser-Support
- ⚡ Performance-optimiert
📚 CSS Grid Lernressourcen
| Resource | Typ | Beschreibung |
|---|---|---|
| CSS Grid Garden | 🎮 Spiel | Interaktives Lernen |
| Scrimba CSS Grid | 🎥 Video | Hands-on Kurs |
| MDN Grid Guide | 📖 Docs | Umfassende Referenz |
🔧 CSS Grid Implementation
Variablen definieren:
:root {
--columns-amount: 12;
--gap-small: 20px;
}
Grid-Container:
.row {
display: grid;
grid-template-columns: repeat(var(--columns-amount), 1fr);
grid-gap: var(--gap-small);
}
Du wirst feststellen, dass wir bereits eine vertikale Verteilung haben. Nun möchten wir noch so was wie zuvor haben und die jeweilige Breite festlegen. Dafür müssen wir unser grid.css einfach wiederum um die breakpoints erweitern. Anstelle von breiten verwenden wir jedoch ein grid-column span.
Responsive Grid-Spans:
/* Mobile */
.col-12 {
grid-column: span 12;
}
/* Small Devices */
@media (min-width: 576px) {
.col-sm-4 {
grid-column: span 4;
}
}
/* Medium Devices */
@media (min-width: 768px) {
.col-md-3 {
grid-column: span 3;
}
}
🎯 Advanced CSS Grid Features
Du hast nun ein Grid ähnliches Layout mit CSS Grid als Basis gebaut. Überlege dir folgende Anforderung. Nebst dem eigentlichen Inhalt, der auf 12 Spalten verteilt ist, möchtest du nun auf einer virtuellen ersten bzw. letzten Spalte bei Bedarf einen "Seiten-Content" einfügen.

Der Rest soll sich weiterhin verteilen wie zuvor. Das Element auf der Seite soll sich an der Höhe orientieren, wo auch der entsprechende Inhalt steht. Überleg dir mal, wie du das ohne CSS Grid machen würdest. Damit das Beispiel funktioniert, kopiere folgendes HTML in dein index.html
<body>
<h1>Responsive is cool</h1>
<div class="row">
<button class="col-12 col-sm-4 col-md-3">button 1</button>
<button class="col-12 col-sm-4 col-md-3">button 2</button>
<button class="col-12 col-sm-4 col-md-3">button 3</button>
<button class="col-12 col-sm-4 col-md-3">button 4</button>
<button class="col-12 col-sm-4 col-md-3">button 5</button>
<button class="col-12 col-sm-4 col-md-3">button 6</button>
<button class="col-12 col-sm-4 col-md-3">button 7</button>
<button class="col-12 col-sm-4 col-md-3">button 8</button>
<button class="col-12 col-sm-4 col-md-3">button 9</button>
<button class="col-12 col-sm-4 col-md-3">button 10</button>
<button class="col-12 col-sm-4 col-md-3">button 11</button>
<button class="col-12 col-sm-4 col-md-3">button 12</button>
<button class="col-12 col-sm-4 col-md-3">button 13</button>
<button class="col-12 col-sm-4 col-md-3">button 14</button>
<button class="col-12 col-sm-4 col-md-3">button 15</button>
<button class="col-12 col-sm-4 col-md-3">button 16</button>
<button class="col-12 col-sm-4 col-md-3">button 17</button>
<button class="col-12 col-sm-4 col-md-3">button 18</button>
<button class="col-12 col-sm-4 col-md-3">button 19</button>
<button class="col-12 col-sm-4 col-md-3">button 20</button>
<button class="col-12 col-sm-4 col-md-3">button 21</button>
<button class="col-12 col-sm-4 col-md-3">button 22</button>
<button class="col-12 col-sm-4 col-md-3">button 23</button>
<button class="col-12 col-sm-4 col-md-3">button 24</button>
<button class="col-12 col-sm-4 col-md-3">button 25</button>
<button class="col-12 col-sm-4 col-md-3">button 26</button>
<button class="col-12 col-sm-4 col-md-3">button 27</button>
<button class="col-12 col-sm-4 col-md-3">button 28</button>
<button class="col-12 col-sm-4 col-md-3">button 29</button>
<button class="col-12 col-sm-4 col-md-3">button 30</button>
<button class="col-12 col-sm-4 col-md-3">button 31</button>
<button class="col-12 col-sm-4 col-md-3">button 32</button>
<button class="col-12 col-sm-4 col-md-3">button 33</button>
<button class="col-12 col-sm-4 col-md-3">button 34</button>
<button class="col-12 col-sm-4 col-md-3">button 35</button>
<button class="col-12 col-sm-4 col-md-3">button 36</button>
<button class="col-12 col-sm-4 col-md-3">button 37</button>
<button class="col-12 col-sm-4 col-md-3">button 38</button>
<button class="col-12 col-sm-4 col-md-3">button 39</button>
<button class="col-12 col-sm-4 col-md-3">button 40</button>
<button class="col-12 col-sm-4 col-md-3">button 41</button>
<button class="col-12 col-sm-4 col-md-3">button 42</button>
<button class="col-12 col-sm-4 col-md-3">button 43</button>
<button class="col-12 col-sm-4 col-md-3">button 44</button>
<button class="col-12 col-sm-4 col-md-3">button 45</button>
<button class="col-12 col-sm-4 col-md-3">button 46</button>
<button class="col-12 col-sm-4 col-md-3">button 47</button>
<button class="col-12 col-sm-4 col-md-3">button 48</button>
<button class="col-12 col-sm-4 col-md-3">button 49</button>
<button class="col-12 col-sm-4 col-md-3">button 50</button>
</div>
</body>
Erweiterte Spalten:
Damit wir unser .aside links bzw. rechts hinzufügen können, müssen wir als erstes dem ganzen Inhalt weitere Spalten hinzufügen. Das einfachste ist, unserer "row" Klasse diese Spalten hinzuzufügen. Dies machen wir nun im grid.css und passen einfach unsere grid-template-columns definition der .row klasse an.
.row {
display: grid;
grid-template-columns: 150px repeat(var(--columns-amount), 1fr) 150px;
grid-gap: var(--gap-small);
}
Nun wird die Page etwa so aussehen:

Wir haben also erreicht, dass wir unseren Inhalt neu auf 14 Spalten verteilen. Was wir aber nicht möchten, ist, dass die erste Spalte durch Inhalt belegt wird. Wir möchten alles eines nach "rechts" verschieben. Wie erreichen wir das rein nur über CSS? Wir wollen keine Klasse im HTML anpassen müssen.
grid-span:
Damit wir das Ziel erreichen, können wir unser CSS mit folgendem Attribut erweitern:
.col-12 {
grid-column: 2 / span 12;
}
Teste die entsprechende Auflösung. Sieht es gut aus? Das ganze benötigen wir nun auch für unsere anderen Auflösungen. Benutze Media Queries und erweitere die col-x Klassen entsprechend. Wir verzichten auf alle col-definitionen, da wir nur entweder 12 spalten, 4 oder 3 brauchen (col-12, col-sm-4, col-md-3).
/* Mobile-Spaltenklassen */
col-12 {
grid-column: 2 / span 12;
}
/* Spaltenklassen für Breakpoint 576px (sm) */
@media (min-width: 576px) {
.col-sm-4 {
grid-column: 2 / span 4;
}
}
/* Spaltenklassen für Breakpoint 768px (md) */
@media (min-width: 768px) {
.col-md-3 {
grid-column: 2 / span 3;
}
}
Teste ein wenig das neue Layout. Folgende Dinge sollten dir auffallen:
- Je nach Grösse des Bildschirms haben wir einen etwas zu grossen gap
- Je nach Grösse des Bildschirms sind die zusätzlichen Spalten am Anfang und Ende zu breit
- Die Höhe der Elemente ist teilweise unterschiedlich
- Die Elemente sind nun immer ab der zweiten Spalte gereiht und nicht mehr verteilt Alle dies Dinge wollen wir nun beheben. Die einfachste ist auf jeden Fall das Gap und die Spaltenbreite.
Passe daher dein css entsprechend an. Wir setzen Variablen ein, die wiederum mit Media-Queries definiert werden. So können wir die Angaben automatisch setzen, basierend auf der Viewport Grösse. Genius 🤓
:root {
--columns-amount: 12;
--column-width: 50px;
--gap-width: 20px;
@media (min-width: 768px) {
--column-width: 150px;
--gap-width: 40px;
}
}
.row {
display: grid;
grid-template-columns: var(--column-width) repeat(
var(--columns-amount),
1fr
) var(--column-width);
grid-gap: var(--gap-width);
}
Gleiche Höhen:
Für das Problem mit der Höhe der Elemente bietet uns CSS Grid eine sehr einfach Eigenschaft an. Mit "grid-auto-rows" und dem Wert "1fr" weisen wir das Grid an, jeweils die selbe Höhe einzusetzen. Basierend auf der Mindest-Höhe, welches ein Element einnimmt. Du kannst beispielsweise bei einem Button den Text so lange machen, dass er noch höher wird. Achte dann darauf, was geschieht. Dafür erweitere einfach das css ganz am Anfang um den entsprechenden Wert.
.row {
display: grid;
grid-auto-rows: 1fr; /* Alle Elemente gleiche Höhe */
grid-template-columns: var(--column-width) repeat(
var(--columns-amount),
1fr
) var(--column-width);
grid-gap: var(--gap-small);
}
Das etwas mühsamere Problem ist, dass wir nun unsere Elemente verteilen möchten, sobald die Breite der Elemente weniger als 12 Spalten einnimmt. Daher für unsere col-sm-4 sowie col-md-3 Klassen benötigen wir eine bessere Lösung.
Wenn wir weiterhin von unserem 12er Grid ausgehen, müssen wir das CSS Grid immer nur für das 4te bzw. 3te Element anweisen, auf die erste Stelle zu springen. Dafür hat CSS den sehr schönen nth-child Selector. Mit diesem können wir genau unser CSS entsprechend erweitern. Passe nun also unsere bisherigen .col-sm-4 bzw. .col-md-3 Klassen so an.
nth-child selector:
.col-sm-4:nth-child(3n+1) {
grid-column: 2 / span 4;
}
.col-md-3:nth-child(4n+1) {
grid-column: 2 / span 3;
}
Wenn du es testet, sieht es irgendwie noch nicht so ganz toll aus. Weisst du wieso? Das Problem liegt leider genau wieder bei unserem neuen Selektor. Das nth-child von bspw. col-sm-4 wird auch bei einer Auflösung von mehr als unserem md-breakpoint appliziert. Wir haben sie also dreifach (col-12, col-sm-4, col-m-3). Wie beheben wir nun das Problem?
Es gibt zwei Varianten. Wir erweitern unsere Media-Queries, dass sie nicht nur ein Min-Width sondern auch ein Max-Width berücksichtigen. Dies hat den Nachteil, dass wir bei Anpassungen nach "oben" zusätzliche Schritte implementieren müssen. Der andere Ansatz ist, dass wir ganz den Mobile First Ansatz verfolgen und von klein nach gross dafür sorgen müssen, dass die CSS Angaben wichtiger sind als die vorherigen. Wir definieren also nicht nur unsere nth-child Elemente die rutschen sollen, sondern auch noch alle die bleiben sollen. Zusätzlich müssen wir auch dafür ein nth-child Selektor einsetzen, damit er als wichtiger betrachtet wird und die anderen Anweisungen überschreibt. Unser css sieht dann wie folgt aus:
/* ... von col-sm-1 */
.col-sm-4:nth-child(1n + 1) {
grid-column: span 4;
}
.col-sm-4:nth-child(3n + 1) {
grid-column: 2 / span 4;
}
…
.col-md-3:nth-child(1n + 1) {
grid-column: span 3;
}
.col-md-3:nth-child(4n + 1) {
grid-column: 2 / span 3;
}
/* ... bis col-md-12 */
Sidebar Implementation:
Erweitere dein HTML innerhalb der .row. Wo genau, spielt keine Rolle. Vergib die Klasse .aside
<div class="aside">I am an edge case</div>
Wie können wir nun das Element auf die Seite stellen? Einfacher als das. Mit unserem grid-column aus dem css grid haben wir nun die freie Möglichkeit, mit column und row zu definieren, wohin das Element soll. Füge im "grid.css" einfach folgende Klasse hinzu.
.aside {
grid-column: 1
}
Sieht toll aus, oder? Du kannst etwas mit der Position spielen, span hinzufügen und/oder auch die Position definieren. Ein Beispiel dafür könnte wie folgt aussehen.
.aside {
grid-column: 1;
grid-row: span 13;
place-self: center;
}
🔄 CSS Flexbox
Die bisherigen Layout Möglichkeiten beziehen sich sowohl auf die horizontale wie auch auf die vertikale Verteilung von Elementen. Man kann es sich auch so vorstellen, dass man damit sehr gut fährt, wenn man das eigentlich Design-Layout umsetzen möchte. Also wo gibt es auf der Seite Inhalt? Gibt es eine Navbar? Gibt es einen Side-Content? Gibt es einen Footer? Mit CSS Grid, Bootstrap oder Spalten Design lassen sich diese Art von Design / Layouts sehr gut strukturieren und umsetzen. Man könnte auch von 2 Dimensionalen Layouts sprechen. Nebst diesen Varianten hält seit einigen Jahren auch Flexbox Einzug im ganzen CSS Universum. Analog CSS Grid kennt der Browser CSS Flexbox direkt und wird unterdessen breit von den Browser unterstützt. Flexbox ist sogar älter als CSS Grid. Bei Flexbox sprechen wir nun eher von einem eindimensionalen Design. Wir können damit sehr gut Elemente auf der selben Ebene layouten. Sei dies auf der vertikalen oder horizontalen Ausrichtung.
🎯 Flexbox vs. Grid
| Feature | CSS Grid | CSS Flexbox |
|---|---|---|
| 📐 Dimensionen | 2D (Zeilen + Spalten) | 1D (Eine Richtung) |
| 🎯 Use Case | Page Layout | Component Layout |
| 📱 Responsive | Breakpoint-basiert | Content-basiert |
| 🔧 Komplexität | Höher | Einfacher |
📚 Flexbox Lernressourcen
🎯 Technologie-Entscheidung
🤔 Die richtige Wahl treffen
Fragen zur Orientierung:
- 🆕 Neues Projekt oder bestehende Libraries?
- 👥 Team-Erfahrung mit welcher Technologie?
- 🎨 Layout-Flexibilität: Immer 12 Spalten oder variabel?
- 📱 Device-Support: Welche Breakpoints nötig?
💡 Empfehlungen:
| Szenario | Empfehlung | Begründung |
|---|---|---|
| 🚀 Neue Website | CSS Grid + Flexbox | Modern, flexibel, performant |
| 🔧 Bestehende Bootstrap-Site | Bootstrap | Konsistenz wahren |
| 🎯 Einfache Layouts | Flexbox | Weniger Overhead |
| 🏗️ Komplexe Layouts | CSS Grid | 2D-Kontrolle |
⚠️ Anti-Patterns vermeiden
- ❌ Bootstrap + CSS Grid Mix (Konflikte)
- ❌ Float-basierte Layouts (veraltet)
- ❌ Table-Layout für Design (nicht semantisch)
🎉 Zusammenfassung
✅ Was du gelernt hast:
- 📱 Mobile First Prinzip
- 🔧 Custom Grid System
- 📊 Breakpoint-Strategien
- 🚀 CSS Grid Modernisierung
- ⚖️ Technologie-Entscheidungen
🎯 Nächste Schritte:
- 💻 Praktisches Üben mit eigenem Projekt
- 🎮 CSS Grid Garden durchspielen
- 🐸 Flexbox Froggy meistern
- 🏗️ Blog-Seite responsive machen
Du bist bereit für modernes, responsives Webdesign! 🌟
🎨 Blog Page mit CSS
🎯 Was wir designen
Analog zum HTML-Teil werden wir uns nicht um alle Elemente kümmern bzw. nur wenn die Zeit reicht.
✅ Diese Elemente setzen wir sicher um:
- Textgrössen inklusive Überschriften etc.
- Video oder Bild als Slide (du kannst aktuell wählen welches Tag du hier verwendest)
- Rubrik
- Weitere Bilder
- Buttons
- Gefällt mir Bereich
- Blog Page Preview inklusive Ansicht für "weitere spannende Artikel"
🌟 Folgende Elemente wenn Zeit vorhanden:
- Autor*innen Bereich
- Tabellen
- Fazit Box
- Produkt Kachel
- Autor*innen Box mit Folgen-Button
- Thema mit Folgen-Button
📐 Zuerst mal das Grobe
Bei CSS macht es Sinn, zunächst das Grobe zu definieren. Du kannst dir das vorstellen wie auch bei einem Schreibprogramm wie Word. Zuerst versuchst du mal Überschriften, Seitenränder etc. zu optimieren und erst später z.B. auch noch Tabellen, Aufzählungen und weiter ganz nach deinem Geschmack.
🗺️ Basis
Setup-Prozess
Damit wir überhaupt CSS ergänzen können, brauchen wir unser CSS:
Schritt 1: Neuen Ordner erstellen
- Neuen Ordner erstellen:
html_blog_page_with_css
Schritt 2: HTML kopieren
- HTML aus der HTML-Übung in den neuen Ordner kopieren
- Alternative: Von GitHub herunterladen
Schritt 3: CSS-Datei erstellen
main.cssim neuen Ordner erstellen
Schritt 4: CSS verknüpfen
Im index.html den CSS-Import ergänzen:
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>blog page 37832</title>
<link rel="stylesheet" href="main.css">
</head>
✅ Jetzt kannst du in diesem CSS Styles für deine Webseite einsetzen!
Das Ding mit den User Agent Stylesheets
Wir wollen als erstes den äusseren Rand der Seite definieren. Nun kriegen wir etwas das Problem, dass es User Agent Stylesheets gibt, die uns allenfalls in die Quere kommen. Wir wollen also nicht einfach einen neuen Rand hinzufügen, sondern den bestehenden anpassen.
Margin-Problem analysieren
Schritt 1: LiveServer starten
- Rechtsklick auf
index.html - "LiveServer" auswählen
Schritt 2: Chrome DevTools öffnen
- Seite in Chrome öffnen
- Entwickler-Werkzeug aktivieren
Schritt 3: Element inspizieren
- Auswahl-Werkzeug aktivieren
- Mit Maus über verschiedene Elemente fahren
- Bei "äusseren" Elementen: Orange Farbe um die ganze Page

Schritt 4: Margin verstehen
- Orange Farben = Margins
- Grüne Farben = Paddings
- Body-Element hat
8px marginals Standard

💡 Erkenntniss: Wir müssen den
body-Selektor übersteuern, um genau diesen Wert zu vergrössern!
CSS-Lösung implementieren
Body-Margin anpassen:
body {
margin: 20px;
}
Teste verschiedene Werte und überprüfe das Ergebnis!
Weitere Anpassungen
Damit die Page immer mehr der Galaxus-Vorlage entspricht, passen wir weitere Elemente an.
iFrame responsive machen
Das eingebundene iFrame ganz am Anfang stört gewaltig durch die definierte Breite und Höhe. Wir wollen dies per CSS definieren.
HTML bereinigen:
- Höhe und Breite aus dem HTML entfernen
CSS-Lösung:
iframe {
width: 100%;
aspect-ratio: 16 / 9;
height: auto;
display: block;
}
⚠️ Wichtig: Min-height müssen wir bei einem iFrame definieren. Ansonsten wird es "zerquetscht"!
Bilder responsive machen
Die Bilder haben auch noch eine falsche Grösse. Definiere den Selektor auf alle imgs:
img {
max-width: 100%;
height: auto;
}
✅ Resultat: Die maximale Breite wird auf 100% der ganzen Seite gesetzt und so "sprengen" die Bilder das Layout in der Breite nicht mehr.
🎯 Nächste Schritte
Nach diesen Grundlagen-Anpassungen:
- Typography verfeinern
- Farben hinzufügen
- Layout optimieren
- Responsive Design implementieren
Die Basis steht - jetzt wird es kreativ! 🚀
📚 Font und Schriftgrösse anpassen
Diese wenigen Anpassungen haben schon einen starken Effekt auf das Optische. Basierend auf der Vorlage müssen wir uns auch noch Schrift und Schriftgrösse anschauen.
Font-Family analysieren
Wenn du z.B. das Titel-Element H2 untersuchst, findest du folgende Definition:
font-family: Galactica, Arial, sans-serif;
Body-Font definieren
Wir ergänzen die Schrift auf unsere ganze Page im CSS mittels dem Body-Selektor:
body {
margin: 20px;
font-family: Galactica, Arial, sans-serif;
}
Computed Styles überprüfen
Wenn du deine Seite nochmals untersuchst, wirst du eine Änderung feststellen. Aber irgendwie stimmt die Schrift noch nicht. Du kannst über "Computed" genau sehen, welche Schrift dein Browser ausgewählt hat.

Fallback-Mechanismus:
- Galactica versuchen → nicht gefunden
- Arial versuchen → ✅ gefunden und verwendet
- sans-serif als letzte Option
⚠️ Problem: Es rendert die Schrift "Arial", da Galactica nicht verfügbar ist.
Custom Font laden
Wir wollen aber die selbe Schrift wie in der Vorlage. Dazu müssen wir die Schrift zuerst laden.
Font-Loading Grundlagen
Referenz: W3Schools CSS3 Fonts
Font-URL finden
Die URL auf den Font ist auf der Galaxus-Seite zu finden:
Original-Pfad: /static/fonts/Galactica/galactica-2024-12-19.woff2
Vollständige URL: https://static.digitecgalaxus.ch/static/fonts/Galactica/galactica-2024-12-19.woff2
@font-face implementieren
@font-face {
font-family: 'Galactica';
src: url("https://static.digitecgalaxus.ch/static/fonts/Galactica/galactica-2024-12-19.woff2") format('woff2');
}

✅ Lade die Page neu und du wirst sehen, dass nun tatsächlich die Galactica-Schrift verwendet wird!
Schriftgrössen und Gewichte definieren
Damit auch die Grössen und font-weight aller Texte stimmen, gehe durch die Elemente aus der Vorlage durch und definiere es entsprechend in deinem CSS.
H1-Element Beispiel
Aus der Vorlage extrahierte Werte:
- font-weight: 650
- font-stretch: normal
- line-height: 36px
- font-size: 30px
h1 {
font-weight: 650;
font-stretch: normal;
line-height: 36px;
font-size: 30px;
}
Systematisches Vorgehen
- H1-H4 Elemente durchgehen
- P-Tags analysieren
- Werte aus Vorlage übernehmen
⚠️ Beachte: Falls du den Lead als H2 und weitere als H3 definiert hast, musst du das beim Kopieren entsprechend berücksichtigen!
Globale Text-Optimierungen
Vermutlich gibt es jetzt immer noch klare optische Unterschiede. Ein paar zusätzliche Definitionen kannst du mit einem * für alle Elemente hinzufügen:
Universal-Selector
* {
text-wrap-mode: wrap;
text-wrap-style: pretty;
overflow-wrap: break-word;
color-scheme: light;
-webkit-font-smoothing: antialiased;
}
P-Tag Letter-Spacing
p {
font-weight: 400;
font-stretch: normal;
line-height: 28px;
font-size: 18px;
letter-spacing: 0.005em;
}
Browser-spezifische Optimierungen
-webkit-font-smoothing
Das -webkit-font-smoothing ist etwas speziell und wird hier nicht unbedingt empfohlen, weil es nicht alle Browser unterstützen.
Aber: Es macht im Chrome einen wesentlichen optischen Unterschied!
A/B Test
Teste es mit und ohne:
- Ohne: Text wirkt beinahe "zu fett" oder "blurry"
- Mit: Schärfere, glattere Darstellung
Weitere Verbesserungen
Die weiteren Definitionen machen die Umbrüche etc. noch etwas besser:
- text-wrap-mode: Intelligentere Textumbrüche
- text-wrap-style: Schönere Zeilenumbrüche
- overflow-wrap: Besseres Verhalten bei langen Wörtern
Typography-Grundlage ist gelegt! 🎉
📐 Breite der Seite
Problem: Zu breite Darstellung
Im Vergleich wirst du feststellen, dass unsere Page zu breit werden kann. Die Vorlage hat beim Text eine eingeschränkte Breite.
Analyse mit DevTools
Versuche mittels dem Dev-Tools in Chrome herauszufinden, welche Werte und wo du sie hinzufügen musst.
HTML-Struktur optimieren
Container-Ansatz
Arbeite hier mit dem div-Element um den eigentlichen Artikel zu umschliessen und vergib dann eine Klasse, auf welcher du das max-width abkopierst.
⚠️ Beachte: Das Slide Video/Bild hat eine andere Breite definiert. Füge also allenfalls zwei verschiedene Klassen dazu oder passe
iframean.
Hero-Bereich Spacing
Wenn du schon beim iframe bist: Du findest auch eine margin-Angabe dazu. Wir wollen zwischen Hero-Bild/Video noch etwas Abstand gewinnen.
CSS-Implementation
Content-Breite definieren
.content-max-width {
max-width: calc(640px + (80px * 2));
margin: 0 auto;
}
iframe optimieren
iframe {
min-width: 100%;
max-width: 1839px;
aspect-ratio: 16 / 9;
height: auto;
display: block;
margin: 0 auto 80px auto;
}
Zentrierung verstehen
Mit margin: 0 auto; kannst du die Elemente zentrieren:
- 0: Kein vertikaler Margin
- auto: Automatische horizontale Zentrierung
Implementierungs-Checklist
- 📦 Container-div um Artikel-Content
- 🎯 Klasse vergeben für max-width
- 🎬 iframe-Spacing anpassen
- 📱 Responsive testen
Layout-Breite ist professionell optimiert! 🎉
🏷️ Rubrik
Rubrik-Styling optimieren
Die Rubrik "Produkttest" ist noch nicht ganz korrekt. Die Farbe stimmt nicht. Ausserdem können wir dafür sorgen, dass sicher immer alles mit Grossbuchstaben dargestellt wird.
Korrekte Farbe finden
Die korrekte Farbe kannst du entweder dem Galaxus Design System entnehmen oder ab der Vorlage suchen/kopieren.
💡 Hinweis: Wir setzen hier und in allen folgenden Styles immer nur das Galaxus Light Theme um. Ansonsten müssten wir sehr viel mehr CSS produzieren.
CSS-Klasse definieren
Definiere eine Klasse, auf welcher du die Werte vergibst und füge sie überall hinzu, wo du eine Rubrik hast im HTML.
CSS-Implementierung:
.content-category {
color: rgb(147, 83, 185);
text-transform: uppercase;
}
HTML-Anwendung:
<p class="content-category">Produkttest</p>
Eigenschaften erklärt
| Eigenschaft | Wert | Beschreibung |
|---|---|---|
| color | rgb(147, 83, 185) | Galaxus-Purple für Kategorien |
| text-transform | uppercase | Automatische Grossschreibung |
✅ Resultat
- Korrekte Purple-Farbe wie im Design System
- Automatische Grossbuchstaben für Konsistenz
- Wiederverwendbare Klasse für alle Rubriken
Rubrik-Styling ist professionell umgesetzt! 🎉
🔘 Buttons
Button Design System
Die Buttons sehen noch nicht ganz so aus wie sie sollten. Häufig gibt es in einem Design System einen "primary" und "standard" oder "secondary" Button. Das ist bei uns nicht anders.
Button-Hierarchie
| Button-Typ | Verwendung | Beschreibung |
|---|---|---|
| Primary | Primäre Aktion | Die gewünschte/erwartete Aktion |
| Secondary | Alternative Aktionen | Weniger prominente Buttons |
⚠️ Realität: Teilweise wird das auch bei uns nicht ganz eingehalten und es gibt mehr als einen Primary Button.
Design-Referenzen
Design System: Galaxus Button Design
Tipp: Da es nicht so einfach ist die nötigen CSS-Werte zu finden, kann es einfacher sein, wenn du das Beispiel im Dev-Tool des Browsers anschaust. Immerhin die Farben findest du einigermassen übersichtlich.
Responsive Considerations
Damit sich die Buttons in jedem Layout korrekt verhalten, werden wir auch mit Responsive Design in Berührung kommen. Mache allenfalls noch das Responsive Web Design Beispiel durch, damit du das besser verstehst.
CSS-Struktur mit Verschachtelung
Um die Varianten zu stylen, können wir uns sowohl die CSS-Verschachtelung sowie den Umstand zu Nutze machen, dass es ein button-Element ist. Damit können wir den "standard" auf dem Button und mittels einer Klasse "primary" als Variante definieren.
Button.css erstellen
Setup-Prozess
Buttons sind gut geeignet, dass wir das ganze CSS aus dem "main" loslösen und ein eigenes dafür erstellen. So hast du den CSS-Code separat und ist einfacher zu verstehen.
Schritt 1: Datei erstellen
buttons.cssFile erstellen
Schritt 2: Import hinzufügen
Im main.css am Anfang des Files:
@import url("buttons.css");
CSS-Implementierung
Danach fügst du nach und nach alle CSS-Definitionen ein, die für Primary und Standard-Button nötig sind. Sinnvoller nutzt du die Macht des Cascadings: Indem du z.B. den Standard-Button als ersten definierst, kannst du danach die Abweichungen für primary und die states hinzufügen (hover, focus, disabled).
Vollständiges Button-CSS:
button {
background-color: #eee;
color: #000;
border: 1px solid #0003;
border-radius: 3px;
padding: 7px 15px;
width: 100%;
cursor: pointer;
text-align: center;
font-family: inherit;
font-size: inherit;
&:hover {
background-color: #ddd;
}
&:focus-visible {
background-color: #ddd;
}
&.primary {
background-color: #444;
color: #fff;
&:hover {
background-color: #000;
}
&:focus-visible {
background-color: #000;
}
}
&.disabled,
&.primary.disabled {
color: #0006;
border: 1px solid #0001;
}
}
CSS-Eigenschaften erklärt
| Eigenschaft | Standard | Primary | Beschreibung |
|---|---|---|---|
| background-color | #eee | #444 | Hintergrundfarbe |
| color | #000 | #fff | Textfarbe |
| border | 1px solid #0003 | Gleich | Rahmen |
| border-radius | 3px | Gleich | Runde Ecken |
| padding | 7px 15px | Gleich | Innenabstand |
| width | 100% | Gleich | Volle Breite |
| cursor | pointer | Gleich | Hand-Cursor |
States und Interaktionen
Hover-States:
- Standard: Helleres Grau (
#ddd) - Primary: Schwarz (
#000)
Focus-States:
- Accessibility: Gleiche Farben wie Hover
- Keyboard-Navigation: Bessere Sichtbarkeit
Disabled-States:
- Transparente Textfarbe:
#0006 - Schwacher Rahmen:
#0001
HTML-Implementation
Am Schluss musst du im HTML überall noch "primary" hinzufügen, wo es ein solcher ist.
HTML-Beispiele
Standard Button:
<button>Standard Button</button>
Primary Button:
<button class="primary">Primary Button</button>
Disabled Button:
<button class="primary disabled">Disabled Primary</button>
Vorlage-Konformität
Hältst du dich an die Vorlage, sind das tatsächlich aktuell alle Buttons als Primary. Eventuell können wir diesen Umstand später noch verbessern.
Button-System ist professionell implementiert! 🎉
❤️ Like Bereich
Responsive Layout-Unterschiede
Der "Like"-Bereich enthält keine neuen Elemente (ausser noch der "# Personen gefällt dieser Artikel"). Vor allem aber definiert der Bereich verschiedene Layouts für Mobile und Desktop, damit alles etwas besser passt.
Layout-Vergleich
| Mobile | Desktop |
|---|---|
![]() | ![]() |
CSS-Methodik: Utility-First
Wir können für diesen Bereich ein eigenes CSS anlegen oder gezielte CSS-Klassen schreiben, die etwas allgemeiner gehalten sind. Dadurch kann die Wiederverwendung erhöht werden.
Utility-First Ansatz
Anstelle: "blog-page-like-button"
Verwende: Mehrere Klassen die den jeweiligen Anwendungszweck beschreiben
Beispiel: "margin-bottom-small" oder ähnlich
CSS-Methodologien
Diese Art von CSS nennt sich "Utility-First" und wird etwa von der CSS-Bibliothek Tailwind und teilweise von Bootstrap verwendet.
Unser Ansatz: Kombination von:
- Utility-Klassen (Utility-First CSS)
- Komponenten-basierten/semantischen Klassen (OOCSS)
📖 Mehr Infos: CSS Methodologies Guide
Utilities.css erstellen
Setup
Erstelle ein neues CSS-File utilities.css und importiere es im main.css:
@import url("utilities.css");
Margin-Werte analysieren
Wenn du die Vorlage auf Desktop-Grösse untersuchst und nach verschiedenen Margins Ausschau hältst, wirst du verschiedene Angaben finden:
| Wert | Bezeichnung | Abkürzung |
|---|---|---|
| 0px | Zero | 0 |
| 16px | Small | s |
| 24px | Medium | m |
| 32px | Large | l |
| 40px | X-Large | xl |
| 64px | XX-Large | xxl |
Koordinatensystem verstehen

Für oben/unten bzw. rechts/links wird gerne y bzw. x benutzt, welches aus dem klassischen Koordinatensystem stammt.
Naming Convention
Für "margin top small" machen wir mt-s draus:
- m = margin
- t = top
- s = small
Utility-Klassen implementieren
Margin-Top Utilities
/* margin-top utilities */
.mt-0 {
margin-top: 0;
}
.mt-s {
margin-top: 16px;
}
.mt-m {
margin-top: 24px;
}
.mt-l {
margin-top: 32px;
}
.mt-xl {
margin-top: 40px;
}
.mt-xxl {
margin-top: 64px;
}
Margin-Bottom Utilities
/* margin-bottom utilities */
.mb-0 {
margin-bottom: 0;
}
.mb-s {
margin-bottom: 16px;
}
.mb-m {
margin-bottom: 24px;
}
.mb-l {
margin-bottom: 32px;
}
.mb-xl {
margin-bottom: 40px;
}
.mb-xxl {
margin-bottom: 64px;
}
Margin Vertical Utilities
/* margin-top and bottom utilities */
.my-0 {
margin-top: 0;
margin-bottom: 0;
}
.my-s {
margin-top: 16px;
margin-bottom: 16px;
}
.my-m {
margin-top: 24px;
margin-bottom: 24px;
}
.my-l {
margin-top: 32px;
margin-bottom: 32px;
}
.my-xl {
margin-top: 40px;
margin-bottom: 40px;
}
.my-xxl {
margin-top: 64px;
margin-bottom: 64px;
}
HTML-Anwendung
Nachdem nun die CSS-Klassen erstellt sind, fülle im HTML nun dort die Klassen ein, wo sie entsprechend für ein Margin sorgen.

✅ Progress Tracking
📋 Stand Like-Bereich:
- ✅ Abstände oben und unten
- ❌ Text unter dem Button zentriert mit richtigen Abständen
- ❌ Breite des Like-Buttons
Text unter dem Button
Text-Alignment
Du hast bereits die nötigen margins für die Abstände definiert und musst sie nur noch korrekt einsetzen im HTML. Allenfalls musst du etwas mit "0" aushelfen, wenn das User Agent Stylesheet dazwischen funkt.
Für's Zentrieren gibt es grundsätzlich verschiedene Varianten. Die einfachste für den Fall ist aber text-align: center.
Text Utilities
/* text utilities */
.text-center {
text-align: center;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.text-justify {
text-align: justify;
}
✅ Updated Progress:
- ✅ Abstände oben und unten
- ✅ Text zentriert mit richtigen Abständen
- ❌ Breite des Like-Buttons
Breite des Bereichs
Desktop-Analyse
Der Bereich ist in den verschiedenen Elementen in der Breite schmaler. Im Prinzip könnte man einfach der ganzen Section eine Breite verpassen. Die Vorlage nutzt aber tatsächlich Einschränkungen auf dem Button und dem Text darunter selbst.
Untersuche mit dem Browser die Elemente. Dann wirst du sehen, dass beim Desktop folgendes steht:
- width:
fit-content - min-width:
200px
Button-CSS erweitern
Da dies sehr spezifisch ist, macht es Sinn, beim button.css eine Spezialbreite einzuführen:
&.width-200-fit {
min-width: 200px;
width: fit-content;
}
HTML erweitern
<button class="primary width-200-fit">❤️ Gefällt mir</button>

Zentrierung korrigieren
Erst jetzt fällt auf, dass der Button eigentlich linksseitig orientiert ist. Auch das sollten wir ändern.
Der Trick ist relativ simpel: Anstelle das "text-center" auf dem Text selbst zu vergeben, setzt du die CSS-Klasse auf dem section container, welcher Button und Text vereint. Somit kriegen alle Elemente zentriert.

🎉 Falls du das von Beginn an so gemacht hast, umso besser!
✅ Final Progress:
- ✅ Abstände oben und unten
- ✅ Text zentriert mit richtigen Abständen
- ✅ Breite des Like-Buttons
Ein ❤️ für Details
Falls du willst und magst, kannst du im Like-Bereich noch 2-3 Details beheben:
Font-Size Anpassung
Wenn du genau hinschaust, ist die Font-Size hier kleiner als üblich. Versuche herauszufinden wie gross und finde einen Weg mit CSS, dies anzupassen.
SVG-Herz im Button
Das Herz im Button ist ein SVG und sollte weiss sein. Zudem gibt es dort noch einen Abstand und alles sollte auf der selben Höhe liegen. All das ist eher schwierig. Wenn du dich fit dafür fühlst, versuche es. Es muss aber auch nicht sein.
Endlösung
Du findest den Endstand der Lösung auf GitHub
Like-Bereich ist responsive und professionell umgesetzt! 🎉
🎯 CSS Variablen
Warum CSS Variablen?
Beim Like-Bereich haben wir sehr viele "statische" Werte für Abstände benutzt und teilweise auch x-fach wiederholt. Dies lässt sich mit Variablen deutlich besser gestalten. Es erlaubt, falls nötig, auch das einfachere Umsetzen von Responsive Design.
Variablen einführen
Setup-Prozess
Als erstes wollen wir all die margin-Angaben durch Variablen ersetzen. Dafür führen wir ein separates CSS-File ein und importieren es wie üblich im main.css.
@import url("variables.css");
Design System Ansatz
Wir haben in den utilities Angaben für margins von xs bis xxl eingesetzt. Diese wollen wir in den Variablen definieren.
In einem top Design System gäbe es einige Pixel-Angaben, die sich für alles wiederholen:
- Set von Variablen für Margins
- Konsistente Spacing-Skala
- Wiederverwendbare Werte
💡 Pragmatischer Ansatz: Wir definieren die Variablen explizit für die Margins. Später können wir diese möglicherweise mit anderen zusammenführen.
CSS Variablen Grundlagen
Referenz: W3Schools CSS Variables
Variablen in CSS sind relativ umfassend und daher auch komplex. Wir konzentrieren uns hier auf die Basics.
Margin-Variablen definieren
Variable Definition
Definiere mit der Angabe :root {} die Angaben für unsere verschiedenen Margins:
/* margin variables */
:root {
--margin-xs: 12px;
--margin-s: 16px;
--margin-m: 24px;
--margin-l: 32px;
--margin-xl: 40px;
--margin-xxl: 64px;
}
Utilities aktualisieren
Danach gehe zum utilities.css und ersetze die fixen Zahlen durch unsere Variablen:
Margin-Top Utilities:
.mt-0 {
margin-top: 0;
}
.mt-s {
margin-top: var(--margin-s);
}
.mt-m {
margin-top: var(--margin-m);
}
.mt-l {
margin-top: var(--margin-l);
}
.mt-xl {
margin-top: var(--margin-xl);
}
.mt-xxl {
margin-top: var(--margin-xxl);
}
Margin-Bottom Utilities:
.mb-0 {
margin-bottom: 0;
}
.mb-s {
margin-bottom: var(--margin-s);
}
.mb-m {
margin-bottom: var(--margin-m);
}
.mb-l {
margin-bottom: var(--margin-l);
}
.mb-xl {
margin-bottom: var(--margin-xl);
}
.mb-xxl {
margin-bottom: var(--margin-xxl);
}
Margin-Right Utilities:
.mr-0 {
margin-right: 0;
}
.mr-xs {
margin-right: var(--margin-xs);
}
.mr-s {
margin-right: var(--margin-s);
}
.mr-m {
margin-right: var(--margin-m);
}
.mr-l {
margin-right: var(--margin-l);
}
.mr-xl {
margin-right: var(--margin-xl);
}
.mr-xxl {
margin-right: var(--margin-xxl);
}
Margin Vertical (Top + Bottom) Utilities:
.my-0 {
margin-top: 0;
margin-bottom: 0;
}
.my-s {
margin-top: var(--margin-s);
margin-bottom: var(--margin-s);
}
.my-m {
margin-top: var(--margin-m);
margin-bottom: var(--margin-m);
}
.my-l {
margin-top: var(--margin-l);
margin-bottom: var(--margin-l);
}
.my-xl {
margin-top: var(--margin-xl);
margin-bottom: var(--margin-xl);
}
.my-xxl {
margin-top: var(--margin-xxl);
margin-bottom: var(--margin-xxl);
}
✅ Funktionalität prüfen
Like-Bereich testen: Prüfe kurz deinen "Like"-Bereich, ob die ganzen Abstände noch passen oder ob doch etwas kaputt ging.
Weitere Optimierungen
Im Prinzip könntest du jetzt das restliche CSS nach weiteren Möglichkeiten für Variablen absuchen:
Erweiterungsmöglichkeiten
| Bereich | Variablen-Optionen |
|---|---|
| Buttons | Border-radius, Padding, Colors |
| Typography | Font-sizes, Line-heights, Font-weights |
| Colors | Brand-colors, Background-colors |
| Layout | Container-widths, Grid-gaps |
Nächste Schritte
Wir lassen weitere Variablen vorerst sein und kümmern uns dafür um ein responsives Layout.
Variable-System etabliert - bereit für responsive Design! 🎉
💃🏼 Responsive is key
Warum Responsive Design?
Wenn wir eine Page für möglichst viele Endgeräte nutzen wollen, sollten wir sie responsive gestalten.
📖 Siehe auch: Responsive Web Design Guide
💡 Falls du nicht klarkommst, nutze den Stand aus GitHub
Mobile First?
Desktop First vs. Mobile First
Wenn wir eine Seite responsive gestalten, sollten wir uns festlegen:
| Ansatz | Beschreibung | Heute empfohlen? |
|---|---|---|
| Desktop First | Zuerst Desktop, dann kleiner | ❌ Veraltet |
| Mobile First | Zuerst Mobile, dann grösser | ✅ Standard |
Was bedeutet Mobile First?
Grundsatz: Wir gehen immer davon aus, dass es ein Mobile-Gerät mit schmalem Screen ist.
Praktische Beispiele:
| Element | Mobile | Desktop | Grund |
|---|---|---|---|
| Margins | 0px | 24px | Mehr Platz verfügbar |
| Button-Breite | 100% | fit-content | Schmaler Screen nutzen |
| Font-Size | 16px | 18px | Bessere Lesbarkeit |
Fazit: CSS für Mobile definieren, dann für breitere Bildschirme erweitern!
Breakpoints definieren
Galaxus Design System
Damit wir überhaupt verschiedene Bildschirmbreiten unterstützen, müssen wir Breakpoints definieren.
Breakpoints = Stellen im Screen, wo wir sagen "hey, jetzt sollte ich etwas am Design anpassen"
Galaxus Breakpoints
Referenz: Galaxus Screen Ranges
| Range | Breite | Geräte |
|---|---|---|
| zero → xs | 0 - 699px | Mobile |
| xs → s | 700px - 999px | Tablet |
| s → m | 1000px+ | Desktop |
Media Queries - Button responsive machen
CSS-Magie: Media Queries
Media Queries ermöglichen, auf den aktuellen Screen dynamisch zu reagieren.
Regel: Bis zum ersten Breakpoint gilt Mobile → da müssen wir nichts Spezielles tun.
Button-Problem analysieren
Der Like-Button ist auf Mobile zu schmal und sollte die ganze Breite einnehmen, damit er optisch besser wirkt.
Browser-Analyse mit Dev Tool:
- bis 700px: Button volle Breite
- ab 700px: Button schmal
Responsive-Verhalten
| zero → xs (699px) | xs (700px) → ∞ |
|---|---|
![]() | ![]() |
Für unseren Button bedeutet dies, wir müssen ihn beim xs-breakpoint auf schmal definieren. Gehe dazu erneut zum button.css file und suche die Stelle raus, die wir mit einem zusätzlichen CSS definiert haben.
CSS-Implementation
Wir können mit einem Media Query den Button so direkt übersteuern, dass er ab dem xs-breakpoint schmal bleibt. Dafür entfernen wir unsere spezialklasse wieder, die wir zuvor angelegt haben.
/* ENTFERNEN: */
&.width-200-fit {
min-width: 200px;
width: fit-content;
}
Media Query hinzufügen
Neues Button.css mit Media Query:
button {
background-color: #eee;
color: #000;
border: 1px solid #0003;
border-radius: 3px;
padding: 7px 15px;
width: 100%; /* Mobile First: Volle Breite */
cursor: pointer;
text-align: center;
font-family: inherit;
font-size: inherit;
&:hover {
background-color: #ddd;
}
&:focus-visible {
background-color: #ddd;
}
&.primary {
background-color: #444;
color: #fff;
&:hover {
background-color: #000;
}
&:focus-visible {
background-color: #000;
}
}
&.disabled,
&.primary.disabled {
color: #0006;
border: 1px solid #0001;
}
/* Responsive Styles */
@media screen and (min-width: 700px) {
width: fit-content;
min-width: 200px;
}
}
Design-Überlegung
Wenn wir das so machen, applizieren wir das Verhalten auf alle Buttons. In der Vorlage ist das eigentlich nicht so. Jedoch ist das tatsächlich nicht mal so schön:

Problem: Die Buttons wirken etwas unsauber und es gibt quasi eine Zick-Zack-Linie.
Insider-Info: Der Bereich wird aktuell bei Clippy tatsächlich neu gestaltet!
HTML bereinigen
Entferne zu guter Letzt die CSS-Klasse im HTML:
<!-- ENTFERNEN: class="width-200-fit" -->
<button class="primary">❤️ Gefällt mir</button>
Resultat
✅ Was haben wir erreicht?
- Mobile First Ansatz implementiert
- Responsive Breakpoints definiert
- Button-Verhalten optimiert
- Sauberer Code ohne Spezialklassen
🔗 Fertige Version
Die komplette Lösung findest du unter: GitHub - Responsive Version
Responsive Design erfolgreich implementiert! 🎉
🔭 Blog Page Preview
Verschiedene Ansichten
Die Blog Page Preview gibt es an verschiedenen Orten und Formaten:
Layout-Vergleich
| Ansicht | Mobile | Desktop |
|---|---|---|
| Einzelne Page | ![]() | ![]() |
| Listenansicht | ![]() | ![]() |
💡 Glück: Die Änderungen sind minimal zwischen den Versionen. Wir können uns auf die Mobile konzentrieren und haben ev. die restlichen Varianten bereits mit abgebildet.
Flexbox implementieren
Lernressourcen
Damit wir das Design umsetzen können, nutzen wir Flexbox:
- Flexbox Froggy 🐸 (Interaktives Tutorial)
- CSS-Tricks Guide 📖 (Komplette Referenz)
Element-Auswahl
Starte auf dem Element, welches diese Blog Page umschliesst.

Utilities erweitern
Flexbox-Utilities hinzufügen
Damit wir Flexbox verwenden können, brauchen wir im utilities.css die folgenden Klassen:
/* Flexbox utilities */
.d-flex {
display: flex;
}
.flex-row {
flex-direction: row;
}
.flex-column {
flex-direction: column;
}
HTML-Implementation
Setze die beiden Klassen d-flex und flex-row auf dem HTML-Element article und prüfe, was passiert.

✅ Resultat: Die Text-Elemente sind schon mal rechts vom Bild. Eigentlich genau was wir möchten!
Bild-Spezialbehandlung
Problem
Das Bild benötigt relativ viele spezielle Behandlungen, damit es passt. Damit du nicht zu viele Klassen schreiben musst, erstelle eine flex-image-aside Klasse.
CSS-Implementation
.flex-image-aside {
flex: 0 1 30%; /* Kurzschreibweise für grow, shrink, basis */
max-width: 144px;
}
HTML-Anwendung
Vergib diese Klasse dem Bild:
<img src="bild.jpg" alt="Beschreibung" class="flex-image-aside">
Flex Direction anpassen
Von Row zu Column
Jetzt müssen wir im "rechten" Bereich wiederum statt nebeneinander das Ganze untereinander haben. Also von "row" zu "column".
Ahnst du schon, wie du das mit Flexbox machen kannst?
Container hinzufügen
Falls dir ein Container fehlt, um diese Elemente zu umschliessen, kannst du ein div HTML-Element nutzen. Diese eignen sich super, um vor allem CSS-Klassen zu setzen.
<article class="d-flex flex-row">
<img src="bild.jpg" alt="Beschreibung" class="flex-image-aside">
<div class="d-flex flex-column">
<h3>Titel</h3>
<p>Beschreibung</p>
</div>
</article>

Bildverzerrung beheben
Typisch Entwicklung: Man löst ein Problem und schafft sich neue... Wunderbar oder?
Problem: Das Bild ist jetzt komplett verzogen.
Lösung: object-fit: cover
.flex-image-aside {
flex: 0 1 30%; /* Kurzschreibweise für grow, shrink, basis */
max-width: 144px;
object-fit: cover; /* Verhindert Verzerrung */
}
Spacing-Optimierungen
Jetzt stören noch die Abstände oben / unten zwischen den Texten sowie sind Bild und Text zu nah aneinander.
Nutze deine Utility-Klassen um die Abstände anzupassen:
- mr-s für rechten Abstand am Bild
- my-0 um vertikale Margins zu entfernen
Danach müssen wir uns noch um die Schriftgrössen kümmern. Die sind jeweils 13 und 20. Zwischen dem Bild und dem Text gibt es einen Abstand von 16px.
Als Margin-Top bei allen Texten sind 4px definiert. Wir brauchen also doch noch so was wie ein xxs.
Anpassungen vornehmen
Damit alles klappt musst du noch die Fonts im fonts.css erweitern. Ausserdem brauchen wir neue Werte für die Font-Weights. Die fonts.css könnte nun so aussehen (die Werte wie 13, 20 kannst du auch durch small, large oder ähnlich ersetzen).
.font-13 {
font-size: 13px;
line-height: 20px;
}
.font-20 {
font-size: 20px;
line-height: 28px;
}
.font-weight-regular {
font-weight: 400;
}
.font-weight-medium {
font-weight: 500;
}
.font-weight-bold {
font-weight: 700;
}
Setzte dann die Klassen im Html alle. Achte darauf dass du die my-0 durch mt-s und mb-0 ersetzen musst, dort wo es ein mt-s gibt.

Nur noch ein paar Details
Jetzt fehlen uns nur noch diese hübschen Linien oben und unten. Das sind einfache Border die wir ebenfalls im utilities.css definieren können. Auch hier könnten die Farben und Dicken jeweils über Variablen ausgelagert werden.
.border-top {
border-top: 1px solid #ddd;
}
.border-bottom {
border-bottom: 1px solid #ddd;
}

Jetzt sind die Linien noch zu nah. Das können wir mit Padding korrigieren, da eine Border zwischen padding und Margin liegt.

Gemäss Vorlag gibt es ein Padding oben und unten von 16px. Erweitere deine utilitites.css mit Paddings und setze danach die Klassen ein.
Da wir für margin und padding die selben Werte nutzen, legen wir sie gleich zusammen in den Variablen zu spaces und nutzen im utilties.css dann dieselbe Variable für Margin und Padding Angaben.
Variables.css
/* general spaces variables */
:root {
--space-xxs: 4px;
--space-xs: 12px;
--space-s: 16px;
--space-m: 24px;
--space-l: 32px;
--space-xl: 40px;
--space-xxl: 64px;
}
Utilities.css
/* margin-top utilities */
.mt-0{
margin-top: 0;
}
.mt-xxs {
margin-top: var(--space-xxs);
}
.mt-xs {
…
…
/* padding-top utilities */
.pt-0 {
padding-top: 0;
}
.pt-xxs {
padding-top: var(--space-xxs);
}
…
…
Setze dann py-s auf das Article Element

Fleissarbeit…
Jetzt hast du alle Grundlagen im CSS, um all die Blog Page Previews zu gestalten. Gehe durch jede Stelle in der Index.html Seite durch, ergänze die Klassen und es sollte soweit passen. Für Desktop könnte man das Bild noch etwas breiter machen. Aber das ist deine Wahl ob du das noch machen möchtest 😊 Viel Erfolg!
Ps. Du findest die fertige Version auf Github https://github.com/rekoch/webEngineerDgEditors/tree/main/public/02_html_css/08_blog_page_preview
🎁 Wrap it up
Erfolgreiche Umsetzung!
Du hast jetzt all die Elemente umgesetzt:
✅ Erfolgreich implementiert:
- Textgrössen inklusive Überschriften etc.
- Video oder Bild als Slide
- Rubrik
- Weitere Bilder
- Buttons
- Gefällt mir Bereich
- Blog Page Preview inklusive Ansicht für "weitere spannende Artikel"
Dabei sehr vieles gelernt:
| Technologie | Was du gelernt hast |
|---|---|
| CSS Variablen | Wartbare, skalierbare Styles |
| Flexbox | Moderne Layout-Techniken |
| Responsive Design | Mobile First Entwicklung |
| Font-Management | Custom Fonts korrekt einbinden |
| Utility-First CSS | Wiederverwendbare Klassen |
🎊 Gratulation - du bist jetzt ein CSS-Profi!
Erweiterte Challenges
Wenn du jetzt ungebremst noch weiter machen möchtest, dann kannst du folgende offene Elemente versuchen schön zu gestalten:
Noch zu implementieren:
- Autor*innen Bereich
- Tabellen
- Fazit Box
- Produkt Kachel
- Autor*innen Box mit Folgen-Button
- Thema mit Folgen-Button
Schwierigkeits-Einschätzung
Von "einfach bis komplizierter" vorgehen:
| Element | Aufwand (0-10) | Begründung |
|---|---|---|
| Beschreibung Bilder | 2 | Optional: Beschreibungstext unter den Bildern. Benötigt noch etwas HTML (figure, figurecaption) sowie CSS für die Darstellung |
| Produkt Kachel | 3 | Sehr ähnlich wie Blog Page Preview (verzichte besser auf die Verfügbarkeits-Anzeige) |
| Fazit Box | 4-6 | Tabelle besser gestalten (pro/kontra). Herausforderung: CSS-Umsetzung. Eventuell CSS Grid lernen → macht Aufwand grösser |
| Thema mit Folgen | 5 | Elemente wie bei Blog Page Preview wiederverwendbar. Neue Herausforderungen: Einrücken und Button rechts vom Text. CSS Grid könnte helfen |
| Autor*innen Box | 6 | Analog "Thema mit Folgen", plus korrekte Darstellung/Grösse des Autor*innen-Bilds. Grid zum Angleichen mit "Thema folgen" |
| Tabellen | 8-10 | Je nachdem wie "schön" die Tabelle aussehen soll, kann dies enorm aufwändig werden |
Empfohlene Reihenfolge:
- ** Beschreibung Bilder** (Quick Win)
- Produkt Kachel (Flexbox-Wiederholung)
- Fazit Box (CSS Grid Lerneffekt)
- Thema mit Folgen (Layout-Komplexität)
- Autor*innen Box (Bild-Optimierung)
- Tabellen (Master-Challenge)
Fertige Lösung
Die "fertige" Blog Page mit dem ganzen CSS findest du unter: GitHub - Completed Blog Page
💡 Hinweis: Du findest unter dem Link allenfalls auch CSS, HTML-Elemente/Lösungen, die nicht Teil des Tutorials direkt sind. Falls du konkret Fragen hast oder es ähnlich machen willst, frage am besten einfach im Kurs nach.
Du hast eine solide CSS-Basis geschaffen - jetzt wird experimentiert! 🚀
Geschichte von JavaScript
JavaScript hat eine recht interessante Geschichte hinter sich. Diese zeigt auch auf, wie früh JavaScript entstanden ist und eigentlich durch den Fortschritt des WWW geprägt wurde. Siehe auch Wiki JavaScript Geschichte
Wie viel hat JavaScript denn nun mit Java zu tun? Etwa so viel wie ein HotDog mit einem Hund → nichts! Dies ist zwar nicht ganz korrekt. Wenn du die Geschichte auf Wikipedia nachliest, findest du heraus wieso. Die damals genannte Skriptsprache "LiveScript" sollte Java ansprechen können. Weil diese Einwicklung von Netscape (=Browser) und Sun (=Java) zusammen kreiert wurde, nannte man sie später JavaScript. Also eine Web-Skript-Sprache, welche Java zudienen soll. Vermutlich weisst du unterdessen, dass dies heute etwas anders funktioniert.
Einführung JavaScript
- Einstieg nach w3schools Einstieg —> wir programmieren eine simple Funktion mit JavaScript.
- Auf welche Arten kann ein Script eingebunden werden w3schools Einbinden JS
- Was ist ein DOM? w3schools DOM
ECMA und JavaScript
Heute gibt es nebst JavaScript den Namen "EcmaScript". Vielleicht hast du davon gehört. ECMA Skript vor, was eine Skriptsprache für den Browser unterstützen soll. Es ist also eine Art Vorgabe. JavaScript ist eine Programmiersprache, die sich an diese Vorgabe möglichst hält. Die beiden Namen sind also nicht das gleiche, werden aber häufig gleich verwendet.
Have Fun with JavaScript
Wie und wo kann Javascript genutzt werden?
- Direkt im Browser. Öffne dazu beispielsweise die Entwicklertools auf Chrome. Danach wechselst du dort auf „console". Direkt in diesem Fenster kannst du JavaScript schreiben und ausführen.
- Mit einer Lauftzeitumgebung wie dies bei Node der Fall ist. Auf dem Computer findest du „node.exe". Starte dies und code direkt drauf los.
== und ===
Time to code. Juhuu. Starte auf deinem Windows oder Mac "node". Gib auf der Konsole nun folgendes ein:
0 == 0;
Überlege dir nun, was das Resultat ist. Mache nun jeweils das selbe, mit allen nachfolgenden Beispielen. Überlege dir immer VOR der Eingabe, was wohl das Resultat sein könnte:
0 == new String("0");
0 == "0";
new String("0") == "0";
null == undefined;
"" == false;
1 == true;
"1" == true;
new String("66") == true;
Jetzt wiederhole die Schritte und benutze anstelle von == jeweils ===
Objekte und Funktionen
Verwende weiterhin jeweils node, um die Beispiele zu programmieren.
var person = {};
//eingeben;
this.person;
//eingeben;
Was lernst du daraus? Mit {} konntest du einfach so ein Objekt erstellen.
JavaScript erstellt aus allem direkt ein Objekt. Damit lässt sich wunderbar ein Objekt auch erweitern. Entgegen bspw. Java, muss dieses Objekt nicht auf einer Klasse definieren.
Gib folgendes ein:
this.person.name = "urs";
this.person;
Genau. Es hat funktioniert. Jetzt hast du einfach so deinem leeren Objekt kurzerhand ein neues Property angefügt und abgefüllt. Auch hier. Keine Klasse sagt, wie dein Objekt auszusehen hat. Ausserdem ist jedes Objekt immer komplett unabhängig von einem anderen. Du hast eine Variable deklariert, jedoch keinen Wert vergeben. Solche Variablen sind in JavaScript zwar vorhanden, aber sind "undefined". Du darfst also nicht versuchen, auf ein weiteres Property zuzugreifen. Du wirst wohl noch oft den Fehler antreffen, dass du auf eine undefined Variable zugreifen willst.
Funktionen
In JavaScript kannst du dir jedes Objekt wie eine Art HashMap in Java vorstellen. Du hast also eine Art Katalog, welcher definiert, welche Werte existieren und was dort abgespeichert ist. Die Werte können dabei andere Objekte oder auch Funktionen sein. Versuche folgendes:
var person = {};
person.adress = { strasse: "ume egge", nummer: 2, ort: "hie" };
this.person;
person.sayHello = function () {
return "hallo du";
};
this.person;
this.person.sayHello();
Funktionen können auf verschiedene Arten erstellt werden.
function doIt() {
return "no";
}
var doItMaybe = function () {
return "still no";
};
var doItDefiniv = function stillBlocking() {
return "I give up";
};
Erste Variante wird gehoistet und kann vorher schon benutzt werden. Andere Varianten nicht. Named hat Vorteile beim Debuggen. Führe alle Varianten mit () aus ->
- Kein grosser Unterschied
- Führe Variante 2 und 3 ohne () aus
- Der Name wird bei Variante 3 entsprechend angezeigt
In JavaScript ist wirklich ALLES ein Objekt. Sogar Funktionen. Lass es uns beweisen und gib folgendes ein:
doItMaybe instanceOf Object
doItMaybe.candidate = 'nid ig'
das erste wird true zurückgeben.
Was denkst, du was nun in doItMaybe drin ist wenn du es ausführst? Auch ein Array verhält sich eigentlich genau gleich wie ein Objekt:
var a = [];
a[0] = 1;
a; //-> Ausgabe prüfen
a[100] = 100;
//-> denkst du das geht?
a; //-> Ausgabe prüfen
Hier musst du immer sehr darauf achten, wenn du bspw. einen Loop hast mit einer Schleifenvariable. Ein Fehler und deine Schleife läuft endlos.
Higher Order Function
Eine Funktion kann eine Funktion enthalten. Ist dies der Fall, nennt man dieses Konstrukt eine "higher Order function". In Frameworks wie Angular oder React wirst du sehr häufig solche Konstrukte vorfinden.
var higherOrderFunction = function (justANumber, func) {
justANumber = justANumber * 1000;
func(justANumber);
}
this.higherOrderFunction(10, function(answer){console.log(answer)};
Das ganze kann auch mit einer Arrow functions ausgeführt werden:
this.higherOrderFunction(10, (answer) => console.log(answer));
Let and const
In einem modernen JavasScript Umfeld soll immer let und const verwendet werden. Man kann Variablen auch mit dem Keyword var erstellen. Dies verursacht jedoch ungewünschte Effekte und soll nicht mehr gemacht werden);
Basics
Damit du den Einstieg in die JavaScript findest, solltest du die Grundlagen verstehen. Wie in jeder Programmiersprache sind dies die üblich verdächtigen wie Variablen, Funktionen, Schleifen etc. JavaScript ist da auch nicht viel anders.
Einerseits findest du auf W3Schools verschiedene Tutorials dazu W3Schools JavaScript und anderseits im GitHub verschiedene Übungen, die du auf deinen Gerät direkt programmieren und ausprobieren kannst.
GitHub Übungen
- gehe auf Github Repo und lade den Code per ZIP File herunter

- entpacke das ZIP lokal
- öffne den folder /public/03_javascript/01_basics direkt im Vs Code

- nun kannst du jedes File Schritt für Schritt in deinem Tempo durchgehen. Es gibt bei den meisten ein Excercise dazu, wo das zuvor gelernte üben kannst
- Starte die "Erklär" Files am Besten lokal auf deinem Gerät mit der Vs Code Debug Funktion. So kannst du durchgehen und siehst auch immer die Ausgabe in der Konsole

- wenn du danach die übungen machst, überprüfe sie indem du mit dem Debugger durchgehst oder mittels dem Befehl
nodegefolgt vom Dateinamen die Datei ausführst.
node 02_variables_excercises.js
🐛 Debuggen von JavaScript
Natürlich ist der ganze JavaScript Code denn du schreibst und schreiben wirst immer direkt korrekt und funktioniert wie du es dir vorgestellt hast. Wenn es in der Ausnahme doch mal nicht so sein sollte, ist es hilfreich zu wissen, wie du Code prüfen und im Besten Fall auch korrigieren kannst.
Früher hättest du dazu mehrheitlich alles mittels "console.log()" machen müssen. Die Methode kann grundsätzlich immer noch passend sein. Gerade wenn es in einem komplexen Code Stück geschieht, wo nicht so klar ist, wann, wie oft etc. es aufgerufen wird.
Debug JS mit Chrome
Die Methode welche den einfachsten Zugang gewährt, ist der Browser selbst. Denn der Browser führt direkt JavaScript aus und unterstützt dich mit vielen sinnvollen Tools.
- Öffne die Seite 02_buttonReactive mit Chrome
- Öffne die Dev Tools (F12, rechtsklick im HTML -> untersuchen)
- Scrolle zu den Buttons am Ende der Seite

- Drücke bspw. auf den "Dieser Artikel gefällt mir!" Button. Du siehst, dass sich die Anzeige ändert und bspw. von "59 auf 60" bzw. zurück auf "59" wechselt
- Damit wir den dahinter liegenden JavaScript Code debuggen können, müsstest du schon fast wissen wie der heisst. Du kannst auch auf gut Glück die Dateien betrachten.
- Wähle im Dev-Tool das Tab "sources"

- In diesem Fall haben wir "Glück" und es gibt den Folder "javascript" und die Datei "buttons.js" umd die es wohl geht. Klicke die Datei an. Der Source-Code wird auf der rechten Seite angezeigt

- Wenn du weiter schaust findest du die Funktion
toggleLikeButtonStatewas wohl unser gesuchtes Stück Code sein dürfte. Klicke danach links beim Zeilenmarker mit der Maus. Du solltest eine Markierung kriegen. - Drücke jetzt nochmals auf den like button. Wenn es geklappt hat, siehst du im Dev Tool nun die Stelle umrandet. Zudem etwas weiter rechts noch die ganzen debug tools. Auf der Seite selbst steht ebenfalls eine Info.

- Mit F10 oder dem button kannst du nun einzeln weiter gehen. Im Code kannst du bei fast allen Elementen mit der Maus darüber fahren und siehst die Details dazu
- Wir haben aktuell am Anfang gestoppt. Daher ist
currentStatenoch nicht gefüllt

- Drücke jetzt auf den Button oder F10 einmal

- Jetzt siehst du das der Zeiger eins weiter gewandert ist und die Variable
currentStateden aktuellen Wert anzeigt. In diesem Fallinactive - Jetzt kannst du beliebig weiter steppen. Beispielsweise ist es dann spannend, ob beim ersten
ifim code das richtige passiert? - Mit jedem Step siehst du auch, wie sich das HTML anpasst, vorausgesetzt, der Schritt ändert auch tatsächlich was. Bei Zeile 50+ werden die Texte, Farben etc. angepasst.
- Gehe mal ganz durch, bis du wieder "normal" bist und drücke denn Like-Button nochmals. Du solltest dann beim
ifin die andere Variante reinlaufen
Was kann ich tun, wenn ich nicht weiss in welchem File die Änderung gemacht wird?
- Wir hatten vorhin "glück" dass wir das File einfach so gefunden haben.
- Lösche den aktuellen Breakpoint im buttons.js file wieder. Dies kannst du auf verschiedene Wege. Das einfachste ist wohl einfach nochmals an der Stelle zu klicken und die Markierung sollte verschwinden
- Gehe dann im Dev-Tool zurück ins Tab "Elements" wo das html angezeigt
- Suche den Button (oder mit dem Fadenkreuz anwählen) damit du an der richtigen Stell im HTML bist
- Mache dann einen Rechtsklick und wähle "Break on" -> "subtree modifications"

- Klicke dann nochmals den Button
- Du solltest an genau der Stelle "breaken" -> pausieren, wo durch das Javascript der Text geändert wird

- Dies ist super hilfreich, wenn du ein Verhalten im HTML prüfen willst und durch welches Javascript das genau ausgelöst wird.
- Im Debug Tool siehst du alle aktiven Breakpoints. Die dom-Breakpoints kannst du so prüfen und auch wieder löschen

Was wenn ich einfach "egal wo im HTML, wenn es geklickt wird will ich das Javascript finden"?
Auch da hilft dir Chrome…
- Lösche allfällig vorhandene Breakpoints (inklusive den vorherigen DOM)
- Gehe dort wo du die DOM Breakpoints siehst weiter nach unten und klappe "Event Listener Breakpoints" auf
- Wähle "mouse" und dann "click"

- Klicke nochmals auf den Button oder auch bewusst dieses Mal auf einen anderen
- Der Debugger sollte dich automatisch dorthin führen, wo tatsächlich der "Start" Code liegt

Und wenn ich aus meinen Code heraus genau sagen will, wo es stoppen soll?
Es kann sein, dass du Zugang zum Code hast, weil du gerade entwickelst. Dann weisst du ev. genau welchen Teil des Skripts du genauer untersuchen möchtest. Alternativ kannst du auch mit VS Code debuggen
Öffne bei dir im VS Code einen Stand mit JavaScript. Entweder das gleiche Beispiel wie oben oder sonst irgendwas anderes. Öffne das JavaScript im VS Code. Gehe beispielsweise zum Buttons.js
Schreibe an der Stelle, an der du den Debugger nutzen willst "debugger;" rein

Öffne die Page mit dem LiveServer und dann das Dev Tool wie vorhin. Drücke einen Button. Der Debugger stoppt an genau der Stelle.
Das kann helfen, wenn du auch verstehen willst, wo dein File/Code genau liegt. Vor allem wenn du mit React, Angular oder ähnlichen Bibliotheken arbeitest, kann es sein, dass dein Code nochmals umgewandelt wird.
Aber Achtung! Du darfst das "debugger" statement natürlich nicht produkiv ausliefern. Sonst stoppt die Page bei allen, die gleichzeitig das Dev Tool öffnen (wenigstens nur dann :D )
Weitere Infos zum Chrome Debug Tool
Noch viel umfassender findest du die Debug Tools Erklärungen hier Chrome Debug Tool
Debug JavaScript mit VS Code
Wenn dein Code nicht wie erwartet funktioniert, hast du zwei Debugging-Optionen: Browser (Chrome) oder VS Code. Der Vorteil beim VS Code Debugging ist, dass du Code korrigieren und sofort die Auswirkungen sehen kannst.
Zusätzliche Ressourcen
Eine grundlegende Anleitung findest du in der offiziellen VS Code Dokumentation. Falls das nicht reibungslos funktioniert, verwendet diese Anleitung zusätzlich den LiveServer und hat sich in Tests bewährt.
Debug Session starten mit VS Code
Vorbereitung
- Projekt öffnen: Öffne den Ordner
public/03_javascript/02_buttonReactivein VS Code
(alternativ ein beliebiges Projekt mit JavaScript und HTML) - LiveServer starten: Starte die
index.htmlmit deinem LiveServer - URL überprüfen: Du solltest die Seite über folgende URL sehen:
http://127.0.0.1:5500/public/03_javascript/03_buttonReactive/index.html
Debug-Verbindung einrichten
- Debug-Panel öffnen: Gehe zum "Debug"-Bereich in VS Code
- Befehlspalette öffnen:
- Mac:
Cmd + Shift + P - Windows/Linux:
Ctrl + Shift + P
- Mac:
- Befehl eingeben: Tippe
Debug: linkin die Befehlszeile - Debug-Link öffnen: Klicke auf das Ergebnis "Debug: Open Link"

- URL eingeben: Kopiere die LiveServer-URL und füge sie ein

- Verbindung bestätigen: Der Browser sollte die Seite öffnen und in VS Code solltest du die "Aufrufliste" sehen

Breakpoints setzen
- JavaScript-Datei öffnen: Gehe zu einer JavaScript-Datei (z.B.
buttons.js) - Breakpoint setzen: Klicke am linken Rand der gewünschten Zeile
→ Ein roter Punkt wird angezeigt
- Code ausführen: Gehe zurück zur Webseite und führe eine Aktion aus (z.B. Button klicken)
- Debugging startet: Der Debugger stoppt an der markierten Stelle

Debug-Steuerung
- Debug-Toolbar: Du siehst eine verschiebbare Button-Leiste zur Debugging-Steuerung

- Step Over (
F8):- Drücke den ersten Button rechts vom Play-Symbol
- Führt die aktuelle Zeile aus und geht zur nächsten
- Step Into (
F7):- Wenn du wissen möchtest, was sich hinter einer Funktion wie
toggleLikeButtonState(button);verbirgt - Springt in die Methode hinein für detaillierte Analyse
- Wenn du wissen möchtest, was sich hinter einer Funktion wie
- Schritt-für-Schritt:
- F7 = Mehr ins Detail (Step Into)
- F8 = Überspringen (Step Over)
Variable inspizieren
- Werte anzeigen:
- Fahre mit der Maus über eine Variable und bleibe stehen
- Der Wert wird direkt angezeigt
- Zusätzlich gibt es oben links eine Übersicht aller Variablen

- Werte ändern:
- Doppelklick auf eine Variable in der Übersicht
- Ändere den Wert und bestätige mit
Enter
- Simulation:
- Hilfreich zum Testen: "Wie würde sich der Code verhalten, wenn...?"
- Ändere Werte temporär zur Simulation verschiedener Szenarien
Weiterführende Informationen
Detaillierte Informationen findest du in der offiziellen VS Code Debugging-Dokumentation.
Tipps
- LiveServer erleichtert das Debugging erheblich
- Breakpoints können jederzeit gesetzt/entfernt werden
- Variablen-Werte können zur Laufzeit geändert werden
- Step Into vs. Step Over je nach gewünschtem Detailgrad verwenden
JavaScript und Aynchronität
Weil ein Browser bereits auf dem Betriebssystem als Applikation läuft, steht den Teilen in JavaScript nur ein Thread zur Verfügung. Ein Thread ist eine Ressource, welche das Betriebssystem einer Applikation für die Ausführung von Rechenoperationen bereit stellt. Hat man nur 1 solchen Thread, können keine Rechenoperationen gleichzeitig behandelt werden. Der Browser kümmert sich aber nebst dem Ausführen von JavaScript auch noch um die Darstellung der Seite mit HTML oder CSS. Wenn du jetzt also bspw. die ersten 100 Zahlen der Fibonacci Folge berechnen willst, würde die Webseite während dieser Zeit "einfrieren" und keine Interaktionen vom Benutzer mehr entgegen nehmen. Dieses "Problem"wird so gelöst, dass der Browser angewiesen wird (mit JavaScript), dass er aufwändigere Operationen nachlagert ausführt. Du weisst dann nicht genau, wann dein Stück Code effektiv ausgeführt wird. Dieses "asynchrone" Verhalten führt im Umgang mit JavaScript zu einer höheren Komplexität.
Du kannst dir so vorstellen, wie wenn du jemanden bittest, dir einen Kaffee zu besorgen. Während der Kaffee "hoffentlich" organisiert wird, arbeitest du weiter. Wenn er dann da ist, wirst du den Kaffee trinken wollen. Die Aktion "Kaffee trinken" definierst du zwar schon bei der Bestellung des Kaffees (das ist deine Absicht), führst sie aber erst aus, wenn der Kaffee da ist.
In JavaScript wird dies so gelöst, indem eine Funktion eine andere Funktion entgegen nimmt. Die Funktion "Kaffee trinken" wird der Funktion "Kaffee bestellen" mitgegeben. Wenn der Kaffee dann da ist, wird er getrunken. Zeitpunkt und Detail der Ausführung wird in der Methode "Kaffee bestellen" definiert, der konkrete Ablauf von "Kaffee trinken" in der Funktion, die übergeben wird.
Unter w3schools Callbacks findest du ebenfalls ein Code Beispiel dazu. Sicher findest du auch auf YouTube Erklär-Videos zu Callbacks.
Die Syntax sieht wie folgt aus:
function functionWithCallback(callback) {
console.log("Funktion mit Callback wird ausgeführt");
callback();
}
function callbackFunction() {
console.log("Callback-Funktion wird ausgeführt");
}
functionWithCallback(callbackFunction);
Die Funktion functionWithCallback nimmt als Parameter eine Funktion entgegen, welche dann ausgeführt wird. Mittels () siehst du, dass es eine Funktion ist. Inhaltlich weiss die Methode nicht, was sie ausführt, bestimmt aber den Zeitpunkt.
Die Funktion callbackFunction ist die Funktion selbst. Ein Beispiel mit dem Kaffee trinken können wir erweitern, dass die Funktion "Kaffee bestellen" der Funktion "Kaffee trinken" auch noch mitgibt, welchen Kaffee genau eigentlich bestellt wurde.
function orderCoffee(callback) {
console.log("Kaffee wird bestellt...");
console.log("Kaffee ist da!");
callback("Milchkaffee");
}
function drinkCoffee(coffeeType) {
console.log(`Kaffee wird getrunken: ${coffeeType} ☕️`);
}
orderCoffee(drinkCoffee);
Die Funktion "drinkCoffee" verlangt also ebenfalls einen Parameter, den sie nutzen will. Sie weiss nicht "was" dort mitkommt. Die "orderCoffee" Funktion muss sich nun also darum kümmern und übergibt den Typ beim Aufruf des Callbacks.
Wieso macht das so nicht viel Sinn? Der Aufruf von "callback" ist aktuell relativ synchron, da Zeile für Zeile abgearbeitet wird. Stell dir vor, es braucht lange bis die Aktion durch ist. In einem technischen Sinne beispielsweise, weil die Aufgabe über einen entfernten Server erfolgt oder eine lang dauernde Datebankabfrage erfolgt. Du kannst so was mit einem "setTimeout()" simulieren.
function orderCoffee(callback) {
console.log("Kaffee wird bestellt...");
setTimeout(() => {
console.log("Kaffee ist da!");
callback("Milchkaffee");
}, 2000);
}
Wir warten nun 2 Sekunden, bis es weiter geht.
Die Syntax wie oben kann zu Problemen mit this führen und ist etwas mühsam, wenn wir das oft brauchen. Darum kennt JavaScript die "Arrow" Funktion.
Anstelle von
orderCoffee(drinkCoffee) übergeben wir den Inhalt von "drinkCoffee" direkt mit der Arrow Syntax
function orderCoffee(callback) {
console.log("Kaffee wird bestellt...");
setTimeout(() => {
console.log("Kaffee ist da!");
callback("Milchkaffee");
}, 2000);
}
orderCoffee((coffeeType) => {
console.log(`Kaffee wird getrunken: ${coffeeType} ☕️`);
});
die ganze drinkCoffee Funktion ist nicht mehr nötig und wird direkt geschrieben. Du musst das nicht und kannst immer noch innerhalb des Arrow Bodys {} eine andere Funktion aufrufen.
🧠 Blog Page Intelligence

Von statisch zu dynamisch
Bisher ist unsere Blog Page statisch - sie kann aufgerufen werden, aber nichts passiert bei Interaktionen. Buttons haben keine Wirkung. Hier kommt JavaScript ins Spiel!
Mittels JavaScript geben wir der Blog Page mehr Intelligenz:
- Design-Anforderungen eleganter lösen
- Buttons mit Aktionen ausstatten
- Datenladung und -speicherung optimieren
🎯 Hauptziele
Kern-Features (Must-have)
-
Diagramm/Tabelle dynamischer gestalten
- Interaktive Elemente
- Responsive Verhalten
- Bessere Benutzererfahrung
-
Alle Buttons mit Wirkung ausstatten
- Like-Funktionalität
- Follow-Buttons
- Feedback für Benutzeraktionen
-
Backend-Kommunikation
- Daten remote laden
- Daten persistent speichern
- API-Integration
Erweiterte Features (Nice-to-have)
Wenn die Zeit gut reicht:
-
Simples Login-System
- Benutzer-Differenzierung
- Personalisierte Like/Follow-Zustände
- Session-Management
-
Einfaches CMS über Backend
- Content direkt editieren
- WYSIWYG-Editor
- Content-Management-Interface
Lernziele
Nach diesem Kapitel wirst du:
- JavaScript für UI-Interaktionen einsetzen können
- API-Kommunikation verstehen und implementieren
- State Management in Frontend-Anwendungen beherrschen
- Event-driven Programming anwenden können
- Den kompletten Entwicklungszyklus von Frontend zu Backend verstehen
🤖 Table Auto Design
Das Problem
Wenn du den letzten Stand aus dem GitHub Repository nimmst, findest du eine Tabelle für die Benchmark-Übersicht.
Die Tabelle startet hier: index.html#L202
Design-Herausforderung
Obwohl die Tabelle funktioniert, hat sie ein fundamentales Problem: Die Breite der Diagramm-Säulen ist fest im HTML hinterlegt.
<p
class="table-background-brown text-right mt-xxs mb-xxs pr-s py-xxs"
style="width: 24%"
></p>
CSS hat hier seine Grenzen - Breiten und Höhen basierend auf "anderen" Elementen zu definieren, kann CSS nicht.
🎯 Ziele der dynamischen Tabelle
Wir möchten eine Tabelle, die folgendes kann:
- Automatische Berechnung: 100% Breite = höchster Wert, alle anderen prozentual abgeleitet
- Dynamische Anpassung: Ändern sich Werte, passt sich das Design automatisch an
- Wiederverwendbarkeit: Tabelle kann kopiert und mit neuen Werten verwendet werden
JavaScript Setup
Grundlagen JavaScript einbinden
Informationen zum Einbinden findest du auf SelfHTML.
Unser Ansatz:
- Script am Ende der Seite laden (bessere Performance - First Contentful Paint)
- Module-System für strukturierte Code-Organisation
Ordnerstruktur erstellen
Erstelle einen javascript Ordner mit folgenden Dateien:
javascript/
├── main.js
└── tables.js
JavaScript verknüpfen
1. In main.js das tables.js importieren:
import "./tables.js";
2. Am Ende von index.html (vor </body>):
<script src="javascript/main.js" type="module"></script>
</body>
💡 Wichtig:
type="module"ermöglicht das Importieren anderer Scripts
✅ Test der Einbindung
In tables.js einfügen:
console.log("tables.js loaded");
Testen:
index.htmlüber LiveServer öffnen- Chrome DevTools → Console
- Seite neu laden
Du solltest den Text sehen! 🥳 Klick rechts auf das Script-Link - es führt direkt zur Datei.

DOM-Manipulation
Das Document Object Model (DOM)
Der Browser stellt JavaScript das DOM zur Verfügung - eine JavaScript-Repräsentation des gesamten HTML. Über diese Schnittstelle lassen sich Inhalte auslesen und manipulieren.
Elemente finden mit querySelectorAll
document.querySelectorAll("[selector]");
Findet alle Nodes mit dem angegebenen Selektor. Mehr dazu: MDN querySelectorAll
Data-Attribute for the Win
Das Problem lösen
Problem: Suche nach div findet zu viele unnötige Elemente.
Lösung: Data-Attribute
Vorteile von Data-Attributen:
- Werden von Screenreadern ignoriert
- Stören HTML-Struktur minimal
- Geben JavaScript mehr "Wissen"
Tabelle markieren
Tabellen-Container markieren:
Dies ist ein neues div Element welches um die bestehende Tabelle erweitert werden muss. Achte darauf, dass du das </div> nach der Tabelle schliesst.
<div data-table-name="benchmark">
<h4 class="font-20 mb-s">XP-Pen Magic Notepad - CPU Benchmark</h4>
<!-- ... restliches HTML ... -->
<p class="font-13 font-color-light">Score (higher is better)</p>
</div>
Jede Column markieren:
<p
class="table-background-brown text-right mt-xxs mb-xxs pr-s py-xxs"
data-table-column
>
720
</p>
💡 Alternative:
data-table-typestattdata-table-namefür verschiedene Tabellentypen
JavaScript-Implementierung
1️⃣ Tabellen finden
document.querySelectorAll("[data-table-name]").forEach((table) => {
// Code für jede Tabelle
});
Foreach siehe foreach Erklärung
2️⃣ Spalten finden und höchsten Wert ermitteln
Jetzt haben wir das Tabellen Element und können innerhalb wiederum all unsere Spalten finden. Wieso brauchen wir das? Damit die Breite der Spalten stimmt, müssen wir zuerst den höchsten Wert finden. Denn dieser bestimmt 100%. Davon abgeleitet können wir dann berechnen, wie viel % Breit die Spalte sein sollte.
Zuerst suchen wir uns alle columns. Danach gehen wir durch alle Spalten durch und merken uns den höchsten Wert. Dass kannst du mit einer lokalen Variable machen. Diese muss mit let definiert sein, wenn du den Werte anpassen willst. Ist der Wert Schreibgeschützt kannst du const nutzen. In unserem Fall brauchen wir let da wir den Wert neu schreiben, wenn er grösser als der vorherige ist.
document.querySelectorAll("[data-table-name]").forEach((table) => {
// neuer Code
const columns = table.querySelectorAll("[data-table-column]");
let columnWidest = 0;
columns.forEach((col) => {
const colWidth = Number(col.innerText);
if (colWidth > columnWidest) {
columnWidest = colWidth;
}
});
// neuer Code
});
Erklärung:
col.innerText: Nur Text, keine HTML-Tags oder LeerzeichenNumber(): Konvertiert Text zu Zahl für Vergleiche- Optional:
trim()für Leerzeichen entfernen
3️⃣ Breiten berechnen und setzen
document.querySelectorAll("[data-table-name]").forEach((table) => {
const columns = table.querySelectorAll("[data-table-column]");
let columnWidest = 0;
columns.forEach((col) => {
const colWidth = Number(col.innerText);
if (colWidth > columnWidest) {
columnWidest = colWidth;
}
});
// neuer Code
columns.forEach((col) => {
const colWidth = col.innerText;
const width = (100 / columnWidest) * colWidth;
col.style.width = `${width}%`;
});
// neuer Code
});
4️⃣ HTML-Style entfernen
Da JavaScript die Breite berechnet, kannst du das style="width: 24%" im HTML entfernen:
<p
class="table-background-brown text-right mt-xxs mb-xxs pr-s py-xxs"
data-table-column
></p>
Im Prinzip kannst du es als Rückfall drin lassen, sollte das JavaScript nicht geladen werden.
Erweiterte Version (Kürzer aber komplexer)
Kompakte Lösung
const maxWidth = Math.max(
...Array.from(columns, (col) => Number(col.innerText))
);
columns.forEach((col) => {
col.style.width = `${(100 * Number(col.innerText)) / maxWidth}%`;
});
Erklärung der neuen Konzepte
Math.max()
MDN Math.max - Gibt den höchsten Wert zurück.
Array.from()
MDN Array.from - Erstellt Array aus Columns und deren innerText-Werten: [720, 789, 1023]
Spread Operator ...
MDN Spread Syntax - "Spreaded" Array-Elemente als einzelne Parameter:
Math.max([1,2,3])❌ funktioniert nichtMath.max(...[1,2,3])✅ funktioniert →Math.max(1,2,3)
Template Literals
`${(100 * Number(col.innerText)) / maxWidth}%`;
Mit ${} können Variablen und Berechnungen direkt im String verwendet werden.
Fazit
| Ansatz | Vorteile | Nachteile |
|---|---|---|
| Ausführlich | Gut verständlich, lernfreundlich | Mehr Code |
| Kompakt | Weniger Code, eleganter | Erfordert mehr JS-Wissen |
Lernempfehlung
- Beginne mit der ausführlichen Version
- Verstehe jeden Schritt
- Erweitere Wissen schrittweise zu kompakteren Lösungen
💡 AI & Google: Du wirst oft auf kompakte Varianten stoßen - je mehr du lernst, desto verständlicher werden sie!
🛎️ Push the Button
🎯 Ziele
Unsere Buttons sehen zwar aus wie Buttons, aber sie machen noch nichts. Wir wollen folgende Funktionen erreichen:
- Artikel Like Button - mit Unlike und Zähler
- Autor:in Folgen Button - mit Unfollow
- Thema Folgen Button - mit Unfollow
Frontend-Focus: In dieser Umsetzung konzentrieren wir uns auf Frontend-Logik. Daten werden nur im Frontend gespeichert - bei einem Page-Reload werden alle Status zurückgesetzt.
Skript Setup
Button-Script erstellen
- Neue Datei erstellen:
buttons.jsimjavascriptOrdner - Import in
main.jshinzufügen - Test mit Console-Output (siehe Kapitel "Table Auto Design")
🐛 Debugging-Optionen
Für lokales Debugging siehe:
Event Listener verstehen
Events Grundlagen
Ein Event ist eine Nachricht wenn eine Aktion stattfindet - wie "Button XY wurde gedrückt". Der Browser kennt Standard-Reaktionen auf Events (z.B. Checkbox an/aus).
Mehr dazu: MDN Event.preventDefault
Events finden
Öffne für mehr Infos
Herausforderung: Nicht jedes Element bietet die selben Events an:
- Button kann geklickt werden
- Textfeld kann beschrieben werden
- Maus kann bewegt werden
Aber wie finde ich heraus, welche Events verfügbar sind?
MDN-Dokumentation durchsuchen
- Button-spezifische API: HTMLButtonElement
- Instance Methods: HTMLButtonElement#instance_methods
- Problem: Der "click" Event ist hier nicht direkt zu finden!
Vererbungskette verstehen
Wichtiger Hinweis: Button erbt Methoden vom Eltern-Element:
"inherits methods from its parent, HTMLElement"
Lösungsweg: Durch die Vererbungskette navigieren:
HTMLButtonElement→HTMLElement→Element- Ziel: Element API - hier finden sich die meisten Events!
Praktische Tipps
| Methode | Beschreibung | Effektivität |
|---|---|---|
| Dokumentation | MDN systematisch durchsuchen | Vollständig aber zeitaufwändig |
| Trial & Error | Events ausprobieren und testen | Schnell für bekannte Events |
| IDE-Support | Autocomplete und IntelliSense nutzen | Sehr praktisch während Entwicklung |
Praxis-Tipp: Die Devise "trial and error" ist oft effizienter als stundenlanges Dokumentation-Studium!
Event Listener erstellen
Schritt 1: Alle Buttons finden und Event Listener registrieren
document.querySelectorAll("button").forEach((button) => {
// Für jeden Button einen Click-Listener erstellen
});
Event Listener hinzufügen: MDN addEventListener
button.addEventListener("click", () => {
// Handle button click
});
Der erste Parameter ist der Event Type, der zweite (nach dem ,) nimmt eine Funktion entgegen, welche ausgeführt werden soll, wenn der Listener greift. Nun wird es etwas kompliziert, wenn du dies im Detail verstehen möchtest… Lies dazu JavaScript und Callbacks oder übernimmt einfach mal nachfolgende Code Snippets.
Erster Test
document.querySelectorAll("button").forEach((button) => {
button.addEventListener("click", () => {
// Handle button click
alert(`Button "${button.textContent.trim()}" wurde geklickt!`);
});
});
Teste im Browser - bei jedem Button-Klick sollte ein Alert erscheinen!
HTML mit Data-Attributen erweitern
Button-Unterscheidung
Wir haben 3 verschiedene Button-Typen. Für bessere JavaScript-Kontrolle erweitern wir das HTML:
Data-Attribute hinzufügen:
data-button-state: "active" oder "inactive"data-button: Button-Name zur Identifikation
HTML-Updates
Like Button
<button
data-button-state="inactive"
data-button="like_article"
class="primary mb-s font-small align-items-center text-center">
Autor:in folgen
<button
data-button-state="inactive"
data-button="follow_author"
class="primary">
Thema folgen
<button
data-button-state="inactive"
data-button="follow_topic"
class="primary">
Spezifische Button-Auswahl
document.querySelectorAll("button[data-button]").forEach((button) => {
// Nur Buttons mit data-button Attribut
});
Button States Management
⚡ Switch-Statement für Button-Typen
Siehe auch JavaScript Switch Statement
document.querySelectorAll("button[data-button]").forEach((button) => {
button.addEventListener("click", () => {
switch (button.dataset.button) {
case "like_article":
break;
case "follow_author":
break;
case "follow_topic":
break;
default:
console.warn(`Unbekannter Button-Typ: ${button.dataset.button}`);
}
});
});
Toggle-Funktionalität
Im nächsten Schritt wollen wir ein einfaches "toggle" einbauen. Das ist der Begriff, wenn zwischen Zuständen gewechselt wird. Meistens zwischen 2 wie bei uns. Von inaktiv auf aktiv und zurück.
Toggle-Funktion erstellen:
Damit der Button tatsächlich "toggled" wollen wir die Funktion toggleButtonState so erweitern, dass sie den nun aktiven State ausliest und ihn "umdreht". Zusätzlich setzen bzw. entfernen wir den primary-button als CSS Klasse. So wird bereits optisch was passieren.
Lies also als erstes den state aus dem Button und speichere ihn als lokale Variable in der Funktion ab.
function toggleButtonState(button) {
const currentState = button.dataset.buttonState;
Nun kannst du mit einem "if" unterscheiden was basierend auf dem aktuellen State passieren soll. Nebst der CSS Klasse setzen wir auch den State zurück.
function toggleButtonState(button) {
const currentState = button.dataset.buttonState;
if (currentState == "active") {
button.classList.add("primary");
button.dataset.buttonState = "inactive";
} else {
button.classList.remove("primary");
button.dataset.buttonState = "active";
}
}
Funktionsaufruf in Switch-Statement:
Nun muss die Funktion noch aufgerufen werden.
switch (button.dataset.button) {
case "like_article":
toggleButtonState(button);
break;
case "follow_author":
toggleButtonState(button);
break;
case "follow_topic":
toggleButtonState(button);
break;
}
CSS-Klassen automatisch togglen
Mit classList.toggle wird das setzen/entfernen der CSS Klasse noch einfacher:
function toggleButtonState(button) {
const currentState = button.dataset.buttonState;
button.classList.toggle("primary");
if (currentState == "active") {
button.dataset.buttonState = "inactive";
} else {
button.dataset.buttonState = "active";
}
}
Button-Texte dynamisch ändern
Text-Parameter hinzufügen
function toggleButtonState(button, activationText, inactivatingText) {
const currentState = button.dataset.buttonState;
button.classList.toggle("primary");
if (currentState == "active") {
button.textContent = activationText;
button.dataset.buttonState = "inactive";
} else {
button.textContent = inactivatingText;
button.dataset.buttonState = "active";
}
}
Erklärungen
- mit
.textContenterhalten wir direkt den Text ohne inneres HTML activationTextundinactivatingTextsind zwei neue Funktions Parameter- Die Texte müssen übergeben werden, damit sie was tun
Text-Konstanten definieren
Diese werden am Anfang des Files definiert und erlauben einfaches lesen der Texte.
const likePageText = "Dieser Artikel gefällt mir!";
const unlikePageText = "Dieser Artikel gefällt mir nicht mehr";
const followAuthorText = "Autor:in folgen";
const unfollowAuthorText = "Autor:in nicht mehr folgen";
const followTopicText = "Thema folgen";
const unfollowTopicText = "Thema entfolgen";
Switch-Statement mit Texten
Damit die Texte stimmen, übergibst du sie aus der Switch Funktion an die Toggle Funktion.
switch (button.dataset.button) {
case "like_article":
toggleButtonState(button, likePageText, unlikePageText);
break;
case "follow_author":
toggleButtonState(button, followAuthorText, unfollowAuthorText);
break;
case "follow_topic":
toggleButtonState(button, followTopicText, unfollowTopicText);
break;
}
Like Button erweitern
Like Counter implementieren
Der Like Button braucht zusätzliche Features:
- Zähler für Likes
- Icon mit Herz/gebrochenem Herz (Kosmetik)
HTML für Counter markieren
Zahl mit span und ID markieren:
Den counter legen wir lokal im JavaScript an, mit dem Bewusstsein, dass die Daten später von einem Server kommen sollten. Dazu müssen wir zudem im HTML die Stelle markieren, wo der Counter steht.
Gehe zur Stelle im HTML wo steht "59 Personen gefällt dieser Artikel".
Wir wollen nur die Zahl steuern können. Dafür ist ein <span> Element perfekt geeignet, da es keine Zeile erstellt. Füge ein span Element um die Zahl ein und vergib die id "data-like-counter".
<span id="data-like-counter">59</span> Personen gefällt dieser Artikel
💡 Wichtig: IDs dürfen nur einmal pro HTML existieren!
Like-spezifische Toggle-Funktion
Der Like Button wird nun eine Sonderbehandlung brauchen. Lege eine neue Function an im JavaScript und nenne sie toggleLikeButtonState. Als Parameter brauchen wir den Button.
function toggleLikeButtonState(button) {
const currentState = button.dataset.buttonState;
const likeCounter = document.querySelector("#data-like-counter");
// Alternative: document.getElementById("data-like-counter");
let currentCount = parseInt(likeCounter.textContent);
if (currentState == "inactive") {
currentCount++;
} else {
currentCount--;
}
likeCounter.textContent = currentCount;
}
Erklärungen
currentCountdient als Zwischenspeicher des aktuellen CountparseIntkonvertiert den Text zu einer Zahl, damit das Rechnen sauber klapptbutton.dataset.buttonStateholt den Wert des Data Attributes mit Namenbutton-state- der
currentStatedient für die Unterscheidung, ob plus oder minus gezählt wird - mit
likeCounter.textContent =wird der neue Wert gesetzt
Integration mit bestehender Toggle-Logik
Handelt es sich um den Like Button, soll die neue Logik aufgerufen werden.
case "like_article":
toggleLikeButtonState(button, likePageText, unlikePageText);
break;
Text und Aussehen korrigieren
Wenn du jetzt ausprobierst, was passiert, wirst du bemerken, dass zwar der Zähler funktioniert, dafür aber der Text und Aussehen nicht mehr stimmen. Das gute ist, die Logik haben wir ja bereits im "toggleButtonState" geschrieben und können sie einfach nochmals aufrufen, bevor wir den Counter setzen.
function toggleLikeButtonState(button) {
const currentState = button.dataset.buttonState;
const likeCounter = document.querySelector("#data-like-counter");
// Alternative: document.getElementById("data-like-counter");
let currentCount = parseInt(likeCounter.textContent);
// bestehende Logik wiederverwenden
toggleButtonState(button, likePageText, unlikePageText);
if (currentState == "inactive") {
currentCount++;
} else {
currentCount--;
}
likeCounter.textContent = currentCount;
}
Refactoring
An der Lösung ist noch etwas unsauber, dass sich die Methode toggleLikeButtonState ums direkte setzten der Texte kümmert. Wir möchten auch hier die Texte als Parameter entgegen nehmen, auch wenn wir hier grundsätzlich "wissen" um welche Texte es sich handelt. Lagerst du die Verantwortung jedoch an den Aufrufer aus, dann ist die Methode "unabhängig" und könnte einfacher umgezogen werden. Erweitere also die Methode genau gleich wie toggleButtonState um die zwei Parameter, übergib dieses an die toggleButtonState Methode und übergib die Werte wieder bei der Switch Methode.
case "like_article":
toggleLikeButtonState(button, likePageText, unlikePageText);
break;
function toggleLikeButtonState(button, activationText, inactivatingText) {
const currentState = button.dataset.buttonState;
const likeCounter = document.getElementById("data-like-counter");
let currentCount = parseInt(likeCounter.textContent);
toggleButtonState(button, activationText, inactivatingText);
Icon-Integration
Das Icon wird aktuell gelöscht, sobald du den Button drückst. Eigentlich möchten wir auch das Icon korrekt setzen. Dafür erstellen wir zuerst einmal das nötige HTML, dass wir dann einsetzen können. Erstelle am Anfang des Scripts je eine Variable für das "filledHeart" und das "brokenHeart".
SVG-Icons vorbereiten
const filledHeart = document.createElement("span");
const brokenHeart = document.createElement("span");
filledHeart.innerHTML = `<svg
fill="none" viewBox="0 0 16 16" width="16" height="16"
class="svg-icon">
<path fill="#fff" fill-rule="evenodd"
d="M13.971 3.029a3.53 3.53 0 0 0-4.983 0L8 4.018l-.988-.989a3.53 3.53 0 0 0-4.983 0 3.54 3.54 0 0 0 0 4.991L8 14l5.972-5.98a3.54 3.54 0 0 0 0-4.991"
clip-rule="evenodd"></path>
</svg>`;
brokenHeart.innerHTML = `<svg
fill="none" viewBox="0 0 16 16" width="16" height="16"
class="svg-icon">
<path fill="#000" fill-rule="evenodd"
d="M13.971 3.029a3.53 3.53 0 0 0-4.983 0L8 4.018l-.988-.989a3.53 3.53 0 0 0-4.983 0 3.54 3.54 0 0 0 0 4.991L8 14l5.972-5.98a3.54 3.54 0 0 0 0-4.991"
clip-rule="evenodd"></path>
<polyline points="6,4 8,8 7,10 9,12" stroke="#fff" stroke-width="1" fill="none"/>
</svg>`;
// Abstand zwischen Icon und Text
filledHeart.classList.add("mr-s");
brokenHeart.classList.add("mr-s");
Erklärungen
createElementerstellt ein HTML Element vom Typ, den du mitgibst- mit
innerHtmlkann HTML angefügt werden - der SVG Code selbst ist eher kommpliziert. Im Prinzip gibt es die Linien vor, die gezeichnet werden
Icons in Toggle-Funktion integrieren
if (currentState == "inactive") {
button.prepend(filledHeart);
currentCount++;
} else {
button.prepend(brokenHeart);
currentCount--;
}
🎉 Ergebnis
Alle Buttons sind jetzt voll funktionsfähig:
✅ Like Button: Toggle mit Counter und Icon
✅ Follow Author: Toggle mit Text-Änderung
✅ Follow Topic: Toggle mit Text-Änderung
Nächste Schritte
Im nächsten Kapitel verbinden wir die Buttons mit einem Backend für persistente Datenspeicherung!
⛓️💥 Backend verbinden
Frontend trifft Backend
Situation verstehen
Frontend-Only: Alles ist einfach - nur ein Datenzustand im Browser:
- Button gedrückt? ✅ HTML/Cookie
- Seite geliked? ✅ Lokaler Speicher
Mit Backend: Zwei Datenzustände synchronisieren:
- 🖥️ Frontend-State (Browser)
- 🗄️ Backend-State (Datenbank)
🎯 Erste Implementierung-Ziele
- Like Count vom Backend laden und korrekt anzeigen
- Blog-Page-ID-Wechsel mit Backend-Daten synchronisieren
Code-Setup
Projekt-Code holen
- Frontend herunterladen: github Frontend
- Backend herunterladen: github Backend
- Alternative: Komplettes GitHub Projekt klonen oder als ZIP herunterladen.

VS Code Settings anpassen
LiveServer-Konfiguration erweitern:
- Settings öffnen: Button unten links → Settings

- LiveServer suchen: "liveServer" eingeben → "In settings.json öffnen"
3. JSON erweitern:
"liveServer.settings.ignoreFiles": [
".vscode/**",
"public/00_backend/**"
]
Achte darauf, dass bei der Zeile davor ein Komma am Schluss ist.

- Speichern und schließen
Optional: SQLite-Editor
SQLite3 Editor Extension installieren für direkten Datenbank-Zugriff.
Projekt-Struktur verstehen
Backend-Struktur (00_backend/)
00_backend/
├── 📄 app.js # Express-Hauptanwendung
├── 📄 package.json # Dependencies & Scripts
├── 📄 .env # Umgebungsvariablen
├── 📄 editorialContent.db # SQLite-Datenbank
├── 📄 readme.md # Setup-Anweisungen
├── 📁 routes/ # API-Endpunkte
│ ├── likes.js # Like-Funktionalität
│ ├── authorFollow.js # Autor-Follow
│ └── topicFollow.js # Topic-Follow
├── 📁 db/ # Datenbank-Layer
│ ├── blogPageLikesRepo.js # Like-Operationen
│ ├── authorFollowRepo.js # Autor-Follow-Ops
│ └── topicFollowRepo.js # Topic-Follow-Ops
└── 📁 utils/ # Middleware & Tools
├── corsMiddleware.js # CORS-Konfiguration
└── swagger.js # API-Dokumentation
Backend starten
# Terminal öffnen
cd public/00_backend
# Dependencies installieren
npm install
# Development-Server starten
npm run dev
# ✅ Backend läuft auf Port 3000
# 📚 API-Docs: http://localhost:3000/api-docs/
Frontend-Struktur (04_01_backendBaseConnection/)
04_01_backendBaseConnection/
├── 📄 index.html # Haupt-HTML
├── 📁 css/ # Styling
├── 📁 javascript/
│ ├── 📄 main.js # Entry Point
│ ├── 📁 services/ # Backend-Services
│ │ ├── 📄 observer.js # Event-System (Singleton)
│ │ ├── 📄 httpClient.js # HTTP-Wrapper
│ │ ├── 📄 blogPageLikes.js # Like-API-Calls
│ │ ├── 📄 authorFollow.js # Autor-Follow-APIs
│ │ └── 📄 topicFollow.js # Topic-Follow-APIs
│ └── 📁 pages/blogPage/ # Blog-spezifische Logik
│ ├── 📄 blogPageMain.js # Koordination
│ ├── 📄 buttons.js # Button-Events
│ ├── 📄 likeStateHandler.js # Like-State
│ ├── 📄 followStateHandler.js # Follow-State
│ └── 📄 tables.js # Tabellen-Management
Architektur-Highlights
Services Layer
- Backend-Integration: Alle API-Calls (erfordert laufenden Backend-Server)
- Observer Pattern: Event-System für applikationsweite Zustandssynchronisation
- Vorteil: Keine Page-Reloads bei Datenänderungen
Event-Driven Architecture
- Observer.js: Singleton-Pattern für typsafe Events
- Auto UI-Updates: Änderungen reflektiert überall
- Performance: Minimale API-Calls durch intelligente Zustandsverwaltung
Moderne JavaScript-Patterns
- ES6-Module: Import/Export-System
- Async/Await: Statt Promise-Chains
- Event Prevention: Verhindert Page-Reloads
Frontend ↔ Backend Workflow
- Frontend → API-Calls über Services
- Backend → JSON-Response
- Observer → Änderungen an UI-Komponenten verteilen
- UI → Automatische Updates ohne Page-Reload
Like Count Implementation
Schritt 1: setLikeCounter-Funktion erstellen
In likeStateHandler.js - am Ende der Datei:
function setLikeCounter() {
// Logik kommt hier rein
}
Schritt 2: BlogPageId-Validierung
function setLikeCounter() {
if (blogPageId) {
// Nur weiter wenn gültige BlogPageId vorhanden
}
}
Schritt 3: HTML-Element finden und Test-Wert setzen
function setLikeCounter() {
if (blogPageId) {
document.getElementById("data-like-counter").textContent = 99;
}
}
Schritt 4: Funktion integrieren
blogPageMain.js erweitern:
// Importiere die notwendigen Module
import "./buttons.js";
import "./tables.js";
import "./likeStateHandler.js";
likeStateHandler.js - am Ende, damit die Funktion aufgerufen wird:
observeUserIdChange();
observeBlogPageIdChange();
observeLikeEvents();
setLikeCounter();
Starte die Page mit dem LiveServer und prüfe, ob du was siehst beim Button unterhalb des "Dieser Artikel gefällt mir!".
Aus 2 Gründen wirst du jetzt nichts sehen:
- Die "blogPageId" ist noch 0 gesetzt, was unser if abfängt
- Später wollen wir sicherstellen, dass wir erst was anzeigen, wenn die Daten stimmen. Darum gibt es ein "invisible" auf dem ganzen Element. Dieses müssen wir entfernen
Schritt 5: BlogPageId setzen & Sichtbarkeit
Test-BlogPageId setzen:
let blogPageId = 37832; // Statt 0
Invisible-Klasse entfernen:
function setLikeCounter() {
if (blogPageId) {
document.getElementById("data-like-counter").textContent = 99;
document.getElementById("like-counter").classList.remove("invisible");
}
}
✅ Test: Du solltest jetzt "99" und den Text sehen!
Backend-Integration
Schritt 1: Service importieren
import { appObserver, ObserverEvents } from "../../services/observer.js";
import { getLikesPerBlogPage } from "../../services/blogPageLikes.js";
Damit kannst du die Daten vom Backend laden.
Schritt 2: Backend-Call implementieren
function setLikeCounter() {
if (blogPageId) {
try {
const response = getLikesPerBlogPage(blogPageId);
document.getElementById("data-like-counter").textContent = response.likeCount;
document.getElementById("like-counter").classList.remove("invisible");
} catch (error) {
console.error("Error loading likes:", error);
}
}
}
** Problem**: Keine Zahl wird angezeigt? Async-Problem! Siehe auch callbacks
Schritt 3: Async/Await Pattern
Problem: Code wartet nicht auf Backend-Response.
Lösung: await für asynchrone Operationen. Zudem musst du die function mit async auszeichnen, damit ein awaiterlaubt ist.
async function setLikeCounter() {
if (blogPageId) {
try {
const response = await getLikesPerBlogPage(blogPageId);
document.getElementById("data-like-counter").textContent = response.likeCount;
document.getElementById("like-counter").classList.remove("invisible");
} catch (error) {
console.error("Error loading likes:", error);
}
}
}
✅ Test: Echte Backend-Daten werden geladen!
Event-Driven Counter Updates
Im Prinzip könnte sich die BlogPageId ändern. Dafür gibt es bereits einen abonnierten Event im likeStateHandler. Anstelle dass wir den Counter beim Laden setzen, nutzen wir den Event. Immer wenn sich die Id ändert laden wir den korrekten Wert aus dem Backend.
BlogPageId-Change-Event nutzen
Direkten Funktionsaufruf entfernen:
observeUserIdChange();
observeBlogPageIdChange();
observeLikeEvents();
// setLikeCounter(); ← Diese Zeile entfernen
Observer-Integration
In observeBlogPageIdChange():
function observeBlogPageIdChange() {
appObserver.subscribe(
ObserverEvents.BLOG_PAGE_ID_CHANGED,
async (data) => {
blogPageId = data.blogPageId;
console.log("Current Blog Page ID in likeStateHandler:", blogPageId);
setLikeCounter(); // ← Hier aufrufen
},
true // Replay-Funktion für Initial-State
);
}
Event-Flow verstehen
main.js→ BlogPageId = 2 setzenmain.js→appObserver.emit()Event versenden- Observer → Alle Subscriber informieren
likeStateHandler→subscribe()mittrue= Replay-Funktion- Replay → Auch nachträglich registrierte Subscriber erhalten letzten Wert
BlogPageId zurücksetzen
let blogPageId = 0; // Wieder auf 0, da Wert von Observer kommt
✅ Test: Likes werden weiterhin geladen, aber jetzt event-driven!
Du kannst dies auch im Console.log überprüfen.

Testing & Experimentation
BlogPageId dynamisch ändern
In main.js experimentieren:
// Verschiedene Blog-Page-IDs testen
appObserver.emit(ObserverEvents.BLOG_PAGE_ID_CHANGED, { blogPageId: 37832 });
Development Workflow
- Backend starten:
cd 00_backend && npm run dev - Frontend öffnen:
index.htmlüber LiveServer - API testen: http://localhost:3000/api-docs/
- Console-Logs beobachten für Event-Flow-Debugging
🎉 Meilenstein erreicht!
✅ Was funktioniert jetzt:
- Backend-Integration - Like-Count aus Datenbank
- Event-Driven Updates - Observer-Pattern implementiert
- Async/Await - Moderne JavaScript-Patterns
- Dynamic BlogPageId - Reaktion auf ID-Änderungen
🚀 Nächste Schritte:
Im nächsten Kapitel machen wir die Like-Buttons funktional und synchronisieren Backend-State mit Frontend-Interaktionen!
❤️🩹 Like-Funktion wiederherstellen
🎯 Überblick
Mit connect the backend hast du dafür gesorgt, dass Like-Counter-Daten aus dem Backend stammen. Der Button selbst funktioniert aber noch nicht korrekt. Wir implementieren die Like-Verwaltung in zwei Schritten:
Ziele:
- Blog-Page liken - mit korrekter UI und Backend-Verbindung
- Blog-Page entliken - mit korrekter UI und Backend-Verbindung
- User-ID-Wechsel - korrekte Reaktion auf Benutzer-Änderungen
Like & Dislike Implementation
Toggle-Logik verstehen
Like und Dislike sind sehr ähnlich - wir togglen zwischen den Zuständen:
- Aktuellen Zustand abfragen (geliked oder nicht?)
- Zustand umkehren (like → unlike, unlike → like)
- UI entsprechend aktualisieren
Architektur-Entscheidung
Das File likeStateHandler.js soll erneut die Haupt Verantwortung dafür übernehmen. Wir haben auch ein File button.js. Dies soll einzig und alleine feststellen, dass der Button gedrückt wurde und danach alles weitere dem likeStateHandler.js überlassen.
File-Aufgabenverteilung:
buttons.js- Nur Button-Klick erkennen und Event versendenlikeStateHandler.js- Komplette Like-Logik verwalten
Um so eine Aufgabe zu delegieren, gibt es grundsätzlich verschiedene Möglichkeiten.
- Wir stellen eine Funktion bereit über "export" die danach von anderen direkt genutzt werden kann
- Wir versenden einen Event und kümmern uns nicht darum, welches File sich der Aufgabe animmt.
Da wir bereits ein Event System zur Verfügung haben, setzen wir Variante 2 um. Diese lässt mehr Spielraum zu.
Event-basierte Kommunikation:
- ❌ Option 1: Direkte Funktionsaufrufe (enger gekoppelt)
- ✅ Option 2: Event versenden (flexibler, entkoppelter)
Like Event versenden
Im button.js File überwachen wir bereits, ob ein Button geklickt wird. Was uns dort noch fehlt ist der Versand unseres Events, damit wir danach im likeStateHandler.js darauf reagieren können.
Button.js erweitern
Observer und Events importieren:
import { appObserver, ObserverEvents } from "../../services/observer.js";
Event bei Button-Klick versenden:
document.querySelectorAll("button[data-button]").forEach((button) => {
button.addEventListener("click", () => {
switch (button.dataset.button) {
case "like_article":
appObserver.emit(ObserverEvents.LIKE_BUTTON_CLICKED);
break;
// ... andere Cases
}
});
});
✅ Test der Event-Übertragung
- Seite mit LiveServer starten
- Like Button klicken
- Console beobachten - Zahlen-Output sollte sich erhöhen
🎉 Erfolg: LikeStateHandler empfängt und verarbeitet dein Event!
Like Event verarbeiten
Du siehst im Console.log wo der Event bereits empfangen wird. Im likeStateHandler.js um Zeile 70 rum. Jetzt müssen wir noch die Logik erweitern, damit es auch das macht, was wir uns erhoffen. Nämlich von dislike auf like und zurück ändern, je nach aktuellem Zustand.
Gehe zur function observeLikeEvents() und ersetze die zuvor implementiere Code Stelle durch den Aufruf einer neuen Funktion die wir toggleLikeState() nennen und im Observer aufrufen.
Toggle-State-Funktion erstellen
Observer-Funktion erweitern:
function observeLikeEvents() {
appObserver.subscribe(ObserverEvents.LIKE_BUTTON_CLICKED, async () => {
toggleLikeState();
});
}
async function toggleLikeState() {
// Logik kommt hier rein
}
Aktuellen Like-Status abfragen
Damit wir nun wissen, was wir tun müssen, brauchen wir den aktuellen Zustand des Likes (hat die Userin die Page geliked oder nicht)? Wir machen so was ähnliches schon im observeBlogPageIdChange bzw. im setLikeCounter. Als erstes holen wir uns den Zustand aus dem Backend. Denke daran, dass wir wieder alles asynchron haben. Erweitere die Funktion wo nötig mit async
Import der benötigten Services:
import {
getLikesPerBlogPage,
getIsLikingBlogPage,
likeBlogPage,
unlikeBlogPage,
} from "../../services/blogPageLikes.js";
Like-Status für User abfragen:
async function toggleLikeState() {
const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
blogPageId,
currentUserId
);
if (doesUserLikeBlogPageResponse.liked) {
// User hat bereits geliked → Unlike
} else {
// User hat noch nicht geliked → Like
}
}
Backend-Status ändern
Jetzt können wir direkt dem Backend mitteilen, dass sich der Zustand geändert hat.
async function toggleLikeState() {
const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
blogPageId,
currentUserId
);
if (doesUserLikeBlogPageResponse.liked) {
await unlikeBlogPage(blogPageId, currentUserId);
} else {
await likeBlogPage(blogPageId, currentUserId);
}
}
UI-Updates implementieren
Das UI braucht noch etwas mehr, damit es jeweils korrekt ist.
Button-UI-Update-Funktion
function updateLikeButtonUi(isLiked) {
const likeButton = document.querySelector(
"button[data-button='like_article']"
);
if (isLiked) {
likeButton.classList.remove("primary");
likeButton.innerHTML = unlikePageText;
likeButton.prepend(brokenHeart.cloneNode(true));
} else {
likeButton.classList.add("primary");
likeButton.innerHTML = likePageText;
likeButton.prepend(filledHeart.cloneNode(true));
}
}
Erklärungen
- damit die Funktion weiss, was zu tun ist, nimmt sie ein Boolean Argument
isLikedentgegen - basierend auf dem Wert, wird entweder der like oder der dislike Zustand angeschrieben
- die Texte sind bereits als
constim File vorhanden - die CSS Klasse
primarymuss entfernt oder hinzugefügt werden
Toggle-Funktion komplettieren
Zuletzt passe den Aufruf im toggleLikeState()an. Am Schluss ergänze den setLikeCounter() Aufruf, der unabhängig des Zustands genutzt werden kann.
async function toggleLikeState() {
const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
blogPageId,
currentUserId
);
if (doesUserLikeBlogPageResponse.liked) {
await unlikeBlogPage(blogPageId, currentUserId);
updateLikeButtonUi(false);
} else {
await likeBlogPage(blogPageId, currentUserId);
updateLikeButtonUi(true);
}
setLikeCounter(); // Counter aktualisieren
}
✅ Funktionstest
Backend und Frontend starten und Like-Button testen!
Error Handling implementieren
Obwohl der Code funktioniert, könnten Fehler im Backend dazu führen, dass wir nicht sauber darauf reagieren. Ein try-catch um die potentiell problematischen Stellen hilft uns hier. Zudem wollen wir sicher gehen, dass wir mit .liked nicht auf ein allfällig nicht vorhandenes Objekt zugreifen.
Dafür umschliessen wir den ganzen Code oben mit einem try und fangen Errors im Catch und geben sie in der Console aus.
Try-Catch für Robustheit
async function toggleLikeState() {
try {
const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
blogPageId,
currentUserId
);
if (doesUserLikeBlogPageResponse.liked) {
await unlikeBlogPage(blogPageId, currentUserId);
updateLikeButtonUi(false);
} else {
await likeBlogPage(blogPageId, currentUserId);
updateLikeButtonUi(true);
}
setLikeCounter();
} catch (error) {
console.error("Error toggling like state:", error);
}
}
prüfen auf undefined
Für den Fall, dass die response komplett leer / undefined ist, machen wir nichts. Nutzen wir den ? Operator bspw. if (doesUserLikeBlogPageResponse.?liked) würde das technisch funktionieren und keinen Fehler mehr geben. In Kombination mit einem if hat es aber zur Folge, dass sowohl bei (nicht vorhanden) wie auch (not liked) ein false ergibt und unsere Logik damit unsauber wäre. Wir prüfen also separat ob es vorhanden ist und stoppen alle weiteren Arbeiten, falls nicht. Das kannst du direkt nach dem abwarten der response machen.
async function toggleLikeState() {
try {
const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
blogPageId,
currentUserId
);
if (!doesUserLikeBlogPageResponse) {
return; // Keine Response erhalten
}
if (doesUserLikeBlogPageResponse.liked) {
await unlikeBlogPage(blogPageId, currentUserId);
updateLikeButtonUi(false);
} else {
await likeBlogPage(blogPageId, currentUserId);
updateLikeButtonUi(true);
}
setLikeCounter();
} catch (error) {
console.error("Error toggling like state:", error);
}
}
User-ID-Wechsel handhaben
Aktuell berücksichtigt die Logik nur eine statische UserId aus dem likeStateHandler.js, die mit 0 gesetzt ist. Ein Wechsel der UserId wird nicht berücksichtigt. Zudem ist der initiale Stand beim Laden der Page potentiell falsch, da dies noch nicht korrekt geladen wird.
User-Change Observer erweitern
Bestehende Observer-Funktion finden:
function observeUserIdChange() {
appObserver.subscribe(
ObserverEvents.USER_ID_CHANGED,
async (data) => {
currentUserId = data.userId;
console.log("Current User ID in likeStateHandler:", currentUserId);
// Hier Like-Status für neuen User laden
},
true // Replay-Funktion für Initial-State
);
}
Like-Status-Check-Funktion erstellen
Auch hier ist es ähnlich wie mit der Blog Page Id. Die UserId wird initial schon im main.js gesetzt. Weil das Abo mit "true" am schluss auch Daten erhält, wenn es erst nachträglich das Abo bezieht, kriegen wir ganz sicher auch einen Initial-State und können darauf reagieren.
Jetzt müssen wir im Prinzip nur herausfinden, welches der aktuelle State ist und das UI entsprechend korrekt darstellen.
Diesen Teil aus der vorherigen Methode macht genau das was wir erneut brauchen:
const doesUserLikeBlogPageResponse = await getIsLikingBlogPage(
blogPageId,
currentUserId
);
if (!doesUserLikeBlogPageResponse) {
return;
}
Wie so oft… wenn es nach "ich kann das nochmals brauchen" klingt, dann schreit es danach, die Funktionalität in eine separate Funktion auszulagern.
Wiederverwendbare Status-Check-Funktion:
Damit wir "von aussen" wissen, ob es einen Fehler gab oder ob die Antwort "true / false" ist, müssen wir unsere neue Funktion etwas smarter gestalten. Da wir im JavaScript ohne weiteres und jederzeit ein Objekt erstellen können, machen wir genau das. Wir erweitern die Antwort der Funktion um:
{success: boolean, liked: boolean, error: string}
Als erstes nutzen wir das, wenn die "response" undefined / null ist. Dann hat irgendwie was nicht geklappt und wir können nicht sagen, ob eine Userin die Page liked oder nicht.
async function checkUserLikeStatus() {
try {
const response = await getIsLikingBlogPage(blogPageId, currentUserId);
if (response === undefined || response === null) {
return { success: false, liked: false, error: "No response received" };
}
} catch (error) {
return { success: false, liked: false, error: error.message };
}
}
Mit return {} erstellen wir ein Objekt, füllen es mit den Properties ab und geben dies zurück. Das ist das coole an JavaScript. Kein "boilerplate" nötig wo wir zuerst mal den definieren müssen, wie das Objekt aussieht. Auf der anderen Seite haben wir auf der "aufrufenden" Seite keine Ahnung, was wir eigentlich als Antwort kriegen…
Das selbe gilt wenn wir in den "catch" laufen. Wir wissen es hat was nicht geklappt und können die Antwort aus dem error direkt weitergeben.
async function checkUserLikeStatus() {
try {
const response = await getIsLikingBlogPage(blogPageId, currentUserId);
if (response === undefined || response === null) {
return { success: false, liked: false, error: "No response received" };
}
} catch (error) {
return { success: false, liked: false, error: error.message };
}
}
Wenn es weder "catch" noch undefined / null gewesen ist, hat es wohl funktioniert. Also geben wir ein sucess:true, den like state aus der response sowie keine Fehlermeldung == null zurück.
async function checkUserLikeStatus() {
try {
const response = await getIsLikingBlogPage(blogPageId, currentUserId);
if (response === undefined || response === null) {
return { success: false, liked: false, error: "No response received" };
}
return { success: true, liked: response.liked, error: null };
} catch (error) {
return { success: false, liked: false, error: error.message };
}
}
Toggle-Funktion refactoren
Status-Check-Funktion verwenden:
async function toggleLikeState() {
try {
const likeStatus = await checkUserLikeStatus();
if (!likeStatus.success) {
console.error("Could not determine like status:", likeStatus.error);
return;
}
if (likeStatus.liked) {
await unlikeBlogPage(blogPageId, currentUserId);
updateLikeButtonUi(false);
} else {
await likeBlogPage(blogPageId, currentUserId);
updateLikeButtonUi(true);
}
setLikeCounter();
} catch (error) {
console.error("Error toggling like state:", error);
}
}
Erklärungen
- wir nutzen neu den
likeStatusum zu wissen, ob wir abbrechen oder nicht - mit dem
.kannst du auf die Properties eines Objekts zugreifen. Hier für den statuslikeStatus.success, den like zustandlikeStatus.likedund fehlermeldunglikeStatus.error
Like State updaten und initialiseren
Jetzt haben wir die Basis erarbeitet, um die Logik in unserer Changed User ID Logik wieder zu verwenden. Gehe zur Funktion observeUserIdChange() wo wir nun als erstes unsere neue Funktion checkUserLikeStatus aufrufen und analog zu vorher weiter verarbeiten.
const likeStatus = await checkUserLikeStatus();
Wenn der Status success ist, können wir direkt das UI updaten und wenn nicht, machen wir nichts bzw. können noch eine Meldung in der Console ausgeben. Zudem rufe noch die setLikeCounter auf. Damit haben wir bereits alles gemacht, um bei einem change der userID alles korrekt zu machen. Starte alles inklusive Backend und teste etwas. Du kannst nun oben rechts die userID anpassen und schauen was passiert.
Dein Code im observeUserIdChange() sollte ungefähr so aussehen.
function observeUserIdChange() {
appObserver.subscribe(
ObserverEvents.USER_ID_CHANGED,
async (data) => {
currentUserId = data.userId;
const likeStatus = await checkUserLikeStatus();
if (likeStatus.success) {
updateLikeButtonUi(likeStatus.liked);
setLikeCounter();
} else {
console.error("Could not fetch like status:", likeStatus.error);
}
},
true
);
}
Was noch fehlt ist das selbe, wenn die blog page id ändert. Dies ist in unserem Fall eigentlich nicht möglich, aber wir wollen den Init über das Main.js nutzen, wo die Blog Page ID gesetzt wird. So ist beim Laden sichergestellt, dass der Like State stimmt.
Ergänze daher auch die Funktion observeBlogPageIdChange() mit dem Setzen des likeStatus und des counters.
💡 Dir fällt sicher auf, dass wir wiederum Code in zwei Methoden haben, der deckungsgleich ist. Das Laden des States und das Setzen.
DRY-Prinzip - Wiederverwendbare Init-Funktion:
Don't repeat yourself. Legen wir dies so zusammen, dass wir es wieder verwenden können. Mittels einer neuen Funktion initLikeState() fassen wir die gemeinsame Logik zusammen und nutzen danach diese.
async function initLikeState() {
const likeStatus = await checkUserLikeStatus();
if (likeStatus.success) {
updateLikeButtonUi(likeStatus.liked);
setLikeCounter();
} else {
console.error("Could not fetch like status:", likeStatus.error);
}
}
Observer-Funktionen finalisieren
User-ID-Change Observer:
function observeUserIdChange() {
appObserver.subscribe(
ObserverEvents.USER_ID_CHANGED,
async (data) => {
currentUserId = data.userId;
await initLikeState();
},
true
);
}
Blog-Page-ID-Change Observer:
function observeBlogPageIdChange() {
appObserver.subscribe(
ObserverEvents.BLOG_PAGE_ID_CHANGED,
async (data) => {
blogPageId = data.blogPageId;
await initLikeState();
},
true
);
}
🎉 Endergebnis
✅ Implementierte Features
- Like/Unlike Toggle - Backend-synchronisiert
- UI-Updates - Button-Text und Icon-Änderungen
- Counter-Updates - Automatische Zähler-Aktualisierung
- User-Switching - Korrekte Like-Status-Anzeige pro User
- Error Handling - Robuste Fehlerbehandlung
- DRY Code - Wiederverwendbare Funktionen
Vollständiger Test
Test-Szenarios:
- ✅ Like/Unlike mit verschiedenen Users
- ✅ User-ID-Wechsel oben rechts
- ✅ Backend-Verbindung aktiv
- ✅ Counter-Updates in Echtzeit
Die Like-Funktionalität ist jetzt vollständig implementiert! 🚀
🚓 Follow-Funktion wiederherstellen
Aufgabe
Du hast gelernt, wie du das Backend für State-Management nutzt - mit Services wie Observer und blogPageLikes. Jetzt fehlt die Follow-Funktionalität für Author und Topic. Nutze dein erworbenes Wissen!
💪 Challenge: Wissen ist Macht, und mit Macht kommt... noch mehr Code! 😄
Author Follow implementieren
Vorbereitung: followStateHandler.js
Das File followStateHandler.js ist bereits vorbereitet:
const followAuthorText = "Autor:in folgen";
const unfollowAuthorText = "Autor:in nicht mehr folgen";
const followTopicText = "Thema folgen";
const unfollowTopicText = "Thema entfolgen";
import { appObserver, ObserverEvents } from "../../services/observer.js";
let currentUserId = 0;
let blogPageId = 0;
function observeUserIdChange() {
appObserver.subscribe(
ObserverEvents.USER_ID_CHANGED,
async (data) => {
currentUserId = data.userId;
console.log("Current User ID in followStateHandler:", currentUserId);
// Weitere Aktionen bei User-ID-Änderung
},
true
);
}
function observeBlogPageIdChange() {
appObserver.subscribe(
ObserverEvents.BLOG_PAGE_ID_CHANGED,
async (data) => {
blogPageId = data.blogPageId;
console.log("Current Blog Page ID in followStateHandler:", blogPageId);
// Weitere Aktionen bei Blog-Page-ID-Änderung
},
true
);
}
observeUserIdChange();
observeBlogPageIdChange();
Integration aktivieren
Das File wird noch nicht geladen! Import in blogPageMain.js hinzufügen:
import "./followStateHandler.js";
Implementation-Aufgaben
1️⃣ Button Event-System erweitern
In buttons.js: Follow-Button-Klicks via Observer weiterleiten
Beispiel für Author-Follow:
case "follow_author":
appObserver.emit(ObserverEvents.AUTHOR_FOLLOW_BUTTON_CLICKED, {
button: button
});
break;
2️⃣ Follow State Handler implementieren
Event empfangen und verarbeiten:
appObserver.subscribe(ObserverEvents.AUTHOR_FOLLOW_BUTTON_CLICKED, async (data) => {
const button = data.button;
// Toggle-Logik implementieren
});
3️⃣ UI korrekt aktualisieren
- Text zwischen follow/unfollow wechseln
- CSS-Klasse
primaryrichtig setzen/entfernen - Backend-State synchronisieren
4️⃣ User/BlogPage-ID-Changes handhaben
Bei Änderungen der User-ID oder Blog-Page-ID korrekten Follow-Status laden und UI aktualisieren.
Hilfreiche Code-Snippets
Author Email aus Button extrahieren
const authorEmail = button.dataset.authorEmail;
Event mit Button-Daten versenden
appObserver.emit(ObserverEvents.AUTHOR_FOLLOW_BUTTON_CLICKED, {
button: button
});
Event mit Button-Daten empfangen
appObserver.subscribe(ObserverEvents.AUTHOR_FOLLOW_BUTTON_CLICKED, async (data) => {
const button = data.button;
const authorEmail = button.dataset.authorEmail;
// Toggle-Logik hier
});
UI-Update-Pattern
function updateAuthorFollowButtonUi(isFollowing, button) {
if (isFollowing) {
button.classList.remove("primary");
button.textContent = unfollowAuthorText;
} else {
button.classList.add("primary");
button.textContent = followAuthorText;
}
}
Topic Follow implementieren
Ähnlich, aber mit Unterschieden
Topic Follow ist fast identisch zu Author Follow, mit diesen wichtigen Unterschieden:
1️⃣ Neuer Event-Typ benötigt
ObserverEvents.TOPIC_FOLLOW_BUTTON_CLICKED
2️⃣ Multiple Button Support
Topics können mehrfach pro Blog-Page vorkommen. Alle Topic-Buttons berücksichtigen:
const buttons = document.querySelectorAll("button[data-button='follow_topic']");
for (const btn of buttons) {
// UI für jeden Button aktualisieren
}
3️⃣ Topic-Name extrahieren
const topicName = button.dataset.topicName;
Implementation-Checkliste
✅ Author Follow
- Observer Events für Author-Follow erstellt
- Button-Click-Events in buttons.js implementiert
- Toggle-Logik in followStateHandler.js
- Backend-Integration (follow/unfollow API-Calls)
- UI-Updates (Text + CSS-Klassen)
- User-ID-Change Handling
- Blog-Page-ID-Change Handling
✅ Topic Follow
- Observer Events für Topic-Follow erstellt
- Multiple Button Support implementiert
- Topic-Name-Extraktion aus Button-Data
- Alle Topic-Buttons bei Status-Änderung aktualisieren
- Backend-Integration vervollständigt
Testing-Szenarios
Ausgiebige Tests durchführen
- User-ID wechseln - Follow-Status sollte sich entsprechend ändern
- Button-Klicks - Follow/Unfollow funktioniert
- Blog-Page-ID ändern - Neue Follow-Status werden geladen
- Multiple Topic-Buttons - Alle werden synchron aktualisiert
- Backend-Persistenz - Status bleibt nach Page-Reload erhalten
🎉 That's it folks!
Wenn alle Tests erfolgreich sind: Gratulation! 🥂
🐛 Debugging-Hilfe
Falls etwas nicht funktioniert:
- Console-Logs überprüfen
- Network-Tab für API-Calls kontrollieren
- Event-Flow durch Observer verfolgen
Endlösung verfügbar
Bei völliger Verzweiflung kannst du die komplette Lösung auf GitHub einsehen:
GitHub: Follow Buttons mit Backend Integration
🏆 Lernziele erreicht
Nach erfolgreicher Implementation beherrschst du:
✅ Event-driven Architecture mit Observer Pattern
✅ Multi-Button State Management
✅ Backend-Frontend-Synchronisation
✅ Dynamic UI Updates
✅ Error Handling & Robustness
Du bist bereit für komplexere Frontend-Herausforderungen! 🚀
💝 Wrap it up
So long and thanks for all the fish
Du hast in diesem JavaScript-Teil grundlegende Funktionen umgesetzt wie:
- Styling einer Tabelle
- Reagieren auf Button-Klicks
- Backend-Anbindung
- Komplette Neugestaltung für eine flüssigere Benutzererfahrung
Refactoring - Der Engineering-Alltag
Und ja, ganz viel Refactoring! Das ist tägliche Arbeit im Engineering-Leben:
- Etwas implementieren
- Weiterentwickeln
- Feststellen: "Oh, das geht ja jetzt einfacher!"
Komplexe Konzepte gemeistert
Du hast fortgeschrittene Konzepte erfolgreich umgesetzt:
- Observer Pattern
- State Management
- Event-driven Architecture
- API-Integration
Wie weiter?
Du hast jetzt ein solides Verständnis für JavaScript und kannst darauf aufbauen.
Option 1: Bestehende Anwendung erweitern
Erweitere das aktuelle Beispiel um weitere Funktionen:
- Komplette Blog-Page aus dem Backend laden
- Zusätzliche Features implementieren
💡 Backend ist bereit: Datenbank, statische Routen etc. sind bereits eingerichtet!
Option 2: Framework verwenden
Nimm dir ein modernes Framework zur Hand und baue die Page damit nach:
| Framework | Beschreibung | Tutorial-Link |
|---|---|---|
| Angular | Vollständiges Framework von Google | Angular Tutorial |
| React | Komponenten-basierte Library von Meta | React Tutorial |
Framework-Vorteile
Vieles, was wir aufwändig manuell gebaut haben, nehmen dir Angular/React ab:
- Automatisches State Management
- Komponenten-System
- Routing
- Form-Handling
Trade-off: Du musst mehr Zeit investieren, um zu verstehen, wie diese Frameworks funktionieren.
🏆 Erreichte Meilensteine
✅ JavaScript-Grundlagen gemeistert
✅ DOM-Manipulation verstanden
✅ API-Integration erfolgreich
✅ Moderne Patterns angewendet
✅ Full-Stack Development erlebt
Congratulations! 🎊 Du bist bereit für den nächsten Schritt in deiner Development-Journey!


Klicke dann auf
und erweitere das JSON so, dass es den Wert fürs Debug Attach auf true hat! 








