Inhalt
Das Problem mit dem Lade-Spinner
Jeder Entwickler kennt den klassischen Ablauf einer Web-Interaktion: Der Nutzer klickt auf einen Button (z. B. "Gefällt mir" oder "Speichern"), ein Lade-Spinner taucht auf, der Browser sendet einen Request an den Server, wartet auf die Datenbank, erhält das "OK" zurück und aktualisiert schließlich das UI.
Auch wenn dein PHP-Backend extrem schnell ist und deine Server-Ladezeiten bei unter 100 Millisekunden liegen: Netzwerklatenzen beim Nutzer (z. B. im mobilen Netz) kannst du nicht kontrollieren. Für den Nutzer fühlt sich die App in diesen Momenten träge und blockiert an.
Die Lösung für dieses Problem, die von Top-Tier SaaS-Produkten wie GitHub, Linear oder Vercel genutzt wird, nennt sich Optimistic UI.
Was ist Optimistic UI?
Der Ansatz ist simpel, aber extrem wirkungsvoll: Wir gehen einfach davon aus, dass der Server-Request erfolgreich sein wird. Anstatt den Nutzer warten zu lassen, aktualisieren wir das User Interface im Bruchteil einer Millisekunde – bevor der Server überhaupt geantwortet hat. Erst im Hintergrund wird der eigentliche AJAX/Fetch-Request an das Backend gesendet.
Sollte der Server (in seltenen Fällen) doch einen Fehler zurückmelden, machen wir die UI-Änderung einfach wieder rückgängig und zeigen eine Fehlermeldung.
Das Ergebnis? Ein "Zero-Latency" Feeling. Die App fühlt sich an, als würde sie lokal auf dem Gerät des Nutzers laufen.
Praxis-Beispiel: Ein "Kudos"-System mit Alpine.js
Bei 3ernert.com setzen wir konsequent auf leichtgewichtige, performante Tools. Anstatt ein gigantisches Framework wie React oder Vue.js für eine einfache Interaktion zu laden, nutzen wir Alpine.js. Es bietet die Reaktivität von Vue, ist aber winzig klein und wird direkt im HTML geschrieben.
Hier ist ein komplettes Beispiel, wie du einen "Gefällt mir"-Button mit Optimistic UI baust:
1. Das Frontend (HTML + Alpine.js + Tailwind CSS)
<div x-data="likeWidget(123, 45, false)" class="flex items-center">
<button
@click.prevent="toggleLike()"
:class="hasLiked ? 'text-fuchsia-500 bg-fuchsia-50' : 'text-slate-500 bg-slate-50'"
class="flex items-center px-4 py-2 rounded-xl font-bold transition-colors">
<svg :class="hasLiked ? 'animate-bounce' : ''" class="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd" />
</svg>
<span x-text="count"></span>
</button>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('likeWidget', (itemId, initialCount, initialHasLiked) => ({
itemId: itemId,
count: initialCount,
hasLiked: initialHasLiked,
async toggleLike() {
// OPTIMISTIC UI: UI sofort aktualisieren!
this.hasLiked = !this.hasLiked;
this.count += this.hasLiked ? 1 : -1;
try {
// Request im Hintergrund absenden
const res = await fetch('/api/like.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ item_id: this.itemId })
});
const data = await res.json();
if (!data.success) {
throw new Error(data.message);
}
// Optional: Synchronisation mit dem echten DB-Wert
this.count = data.count;
} catch(e) {
// FEHLER-HANDLING: Aktion rückgängig machen, falls der Server streikt
this.hasLiked = !this.hasLiked;
this.count += this.hasLiked ? 1 : -1;
alert('Ein Fehler ist aufgetreten: ' + e.message);
}
}
}))
})
</script>
2. Das Backend (PHP 8)
Das Backend muss lediglich den Status in der Datenbank umschalten und die neue Gesamtanzahl als JSON zurückgeben.
<?php
// api/like.php
session_start();
header('Content-Type: application/json');
// JSON Input lesen
$data = json_decode(file_get_contents('php://input'), true);
$item_id = $data['item_id'] ?? null;
$user_id = $_SESSION['user_id'] ?? null;
if (!$user_id || !$item_id) {
echo json_encode(['success' => false, 'message' => 'Ungültige Anfrage oder nicht eingeloggt.']);
exit;
}
try {
// 1. Prüfen, ob der Like bereits existiert
$stmt = $pdo->prepare("SELECT id FROM likes WHERE item_id = ? AND user_id = ?");
$stmt->execute([$item_id, $user_id]);
$existingLike = $stmt->fetch();
if ($existingLike) {
// Unlike
$pdo->prepare("DELETE FROM likes WHERE id = ?")->execute([$existingLike['id']]);
} else {
// Like
$pdo->prepare("INSERT INTO likes (item_id, user_id) VALUES (?, ?)")->execute([$item_id, $user_id]);
}
// 2. Neue Gesamtanzahl zählen
$count = $pdo->prepare("SELECT COUNT(*) FROM likes WHERE item_id = ?");
$count->execute([$item_id]);
echo json_encode(['success' => true, 'count' => $count->fetchColumn()]);
} catch (\Exception $e) {
echo json_encode(['success' => false, 'message' => 'Datenbankfehler.']);
}
Fazit: UX ist König
Die Implementierung von Optimistic UI erfordert nur wenige Zeilen mehr Code im Frontend, verändert die wahrgenommene Performance einer Anwendung aber radikal. Es ist genau diese Liebe zum Detail, die den Unterschied zwischen einer "ok" programmierten Standard-Website und einem High-End Digital-Erlebnis ausmacht.
Wir bei 3ernert.com glauben fest daran, dass Webentwicklung mit Herz gemacht werden muss. Keine generischen Templates, kein Auto-Pilot – stattdessen durchdachte Architekturen und absolute Nutzerzentrierung.
*Braucht dein nächstes Projekt auch einen Performance-Boost? [Lass uns reden!]
Kommentare (0)
Diskutiere mit!
Logge dich ein oder erstelle kostenlos ein Konto, um an der Unterhaltung teilzunehmen und dein Wissen zu teilen.
Noch keine Kommentare vorhanden. Sei der Erste!