Progressive Datei-Uploads: UX- und Technik-Guide
Bauen Sie progressive Upload-Erfahrungen mit Drag-and-drop, Fortschrittsbalken und sauberer Fehlerbehandlung für eine bessere Nutzererfahrung.
Laut BSI-Empfehlungen zur sicheren Datenübertragung brechen Nutzer Upload-Vorgänge ohne sichtbares Feedback typischerweise nach wenigen Sekunden ab — ein messbares UX-Problem mit konkreter Lösung. Ein progressiver Datei-Upload gibt Nutzern an jedem Schritt unmittelbares, verlässliches Feedback: beim Ablegen der Datei eine sofortige Bestätigung, während des Uploads ein flüssiger Fortschrittsbalken mit realistischer ETA, bei Fehlern spezifische Retry-Optionen, nach Abschluss ein klarer Erfolgs-Zustand mit nächsten Aktionen.
Die technischen Bausteine sind: Drag-and-drop via der HTML5-DataTransfer-API, Chunked Uploads mit Wiederaufnahme-Fähigkeit, fetch-Streaming mit ReadableStream für byte-genauen Fortschritt und ein Zustandsmanagement, das Tab-Neuladen via IndexedDB übersteht. Richtig umgesetzt zweifelt ein Nutzer, der 5 GB hochlädt, nie daran, ob die Anwendung eingefroren ist.
Was „progressiv" hier wirklich bedeutet
Der Begriff hat zwei Bedeutungsebenen. Erstens: Progressive Enhancement — der Upload funktioniert als einfaches <input type="file">-POST auf einem Browser von 2012 und erhält Drag-and-drop, Chunking und Retry-Logik, sobald JavaScript verfügbar ist. Zweitens: Progressive Disclosure — die Oberfläche zeigt Komplexität nur bei Bedarf. Prozentangaben erscheinen während des Uploads, Retry-Details erst beim Auftreten eines Fehlers.
Beide Bedeutungen zeigen auf dasselbe Prinzip: Nutzer sollten nie in einer Sackgasse landen und nie ohne Information warten müssen.
Der häufigste Fehler ist der „Spinner des Doom" — eine generische Ladeanimation, die weder Fortschritt noch ETA noch Erfolg oder Misserfolg anzeigt. Nutzer brechen Uploads ab, denen sie nicht vertrauen.
Drag-and-Drop, das nicht gegen den Browser kämpft
Die HTML5-Drag-and-Drop-API ist berüchtigt für Bugs. Einige Regeln machen sie beherrschbar:
const dropzone = document.querySelector('.dropzone');
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('dragging');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('dragging');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('dragging');
handleFiles([...e.dataTransfer.files]);
});
preventDefault auf dragover ist zwingend — ohne es akzeptiert das Drop-Ziel keine Dateien. Nutzen Sie e.dataTransfer.items statt files, wenn Sie Ordner via webkitGetAsEntry() unterstützen möchten — das ist der einzige Weg, Verzeichnisinhalte rekursiv in Chrome und Firefox zu erfassen.
Der Fallback muss ebenfalls benutzbar sein: ein sichtbares <label>, das ein gestaltetes <input type="file" multiple> umschließt, funktioniert für 100 % der Nutzer inklusive Tastatur- und Screenreader-Navigation.
Fortschritt, dem Nutzer vertrauen
Fortschrittsbalken springen aus drei Gründen: ungleichmäßige Chunk-Größen, TCP Slow Start und Buffering im Netzwerk-Stack. Ein gleitender 2-Sekunden-Durchschnitt glättet das Bild:
const samples = []; // [{ time, bytes }]
function recordSample(bytes) {
const now = performance.now();
samples.push({ time: now, bytes });
while (samples.length > 1 && now - samples[0].time > 2000) samples.shift();
}
function throughput() {
if (samples.length < 2) return 0;
const delta = samples[samples.length - 1];
const base = samples[0];
return (delta.bytes - base.bytes) / ((delta.time - base.time) / 1000);
}
Berechnen Sie die ETA als (totalBytes - uploadedBytes) / throughput(), begrenzen Sie die Anzeige auf mindestens 5 Sekunden und formulieren Sie menschlich: „etwa 2 Minuten" statt „124,3 Sekunden." Zeigen Sie sowohl Prozentsatz als auch Byte-Zähler an — Nutzer prüfen beide gegeneinander, wenn etwas seltsam wirkt.
Fehlerzustände mit umsetzbarer Lösung
Generische „Upload fehlgeschlagen"-Meldungen zerstören das Vertrauen. Klassifizieren Sie Fehler in fünf Gruppen:
- Netzwerkabbruch (offline-Ereignis, TCP-Reset): „Verbindung wird wiederhergestellt…" mit automatischem Retry
- Server 5xx: „Serverfehler, nächster Versuch in 5 s" mit manuellem Retry-Button
- Server 4xx (413 zu groß, 415 falscher Typ): „Datei abgelehnt: zu groß" mit Datei-Ersatz
- Authentifizierung abgelaufen (401, 403): „Sitzung abgelaufen — bitte neu anmelden"
- Client-Absturz (JS-Fehler, Browser beendet Tab): Wiederherstellung aus IndexedDB beim Neuladen
Jede Meldung wird gepaart mit genau der einen Aktion, die das Problem löst.
Byte-Tracking mit Fetch Streams
XMLHttpRequest.upload.onprogress ist der klassische Ansatz, aber er ist bei HTTP/3 unzuverlässig und erfasst keine Bytes, die im Sendepuffer warten. Der moderne Ansatz nutzt ReadableStream, um Bytes beim Erzeugen zu zählen:
function trackedStream(blob, onBytes) {
let sent = 0;
return new ReadableStream({
async pull(controller) {
const reader = blob.stream().getReader();
while (true) {
const { done, value } = await reader.read();
if (done) { controller.close(); return; }
sent += value.byteLength;
onBytes(sent);
controller.enqueue(value);
}
}
});
}
Übergeben Sie den Stream als body an fetch mit duplex: 'half'. Safari-Support für Request Streams kam mit Version 17.4; davor ist XMLHttpRequest der Fallback. Dies liefert millisekunden-genauen Fortschritt, der an tatsächlich an den Netzwerk-Stack übergebene Bytes gebunden ist.
Pause, Fortführen und Abbrechen
Nutzer erwarten eine Pause-Taste bei allem, was mehr als eine Minute dauert. Bei Chunked Uploads bedeutet Pause „keine neuen Chunks absenden"; Fortführen setzt die Warteschlange fort. Abbrechen nutzt AbortController:
const ctrl = new AbortController();
cancelButton.onclick = () => ctrl.abort();
await fetch(url, { method: 'PUT', body: blob, signal: ctrl.signal });
Beim Abbruch aufräumen: Upload-Session auf dem Server löschen, IndexedDB-Eintrag leeren, in den Anfangszustand zurücksetzen. Pause bewahrt den Zustand; Abbrechen vernichtet ihn — machen Sie diesen Unterschied in der Oberfläche sichtbar.
Tab-Neuladen überleben
Speichern Sie den Upload-Zustand nach jedem erfolgreichen Chunk:
await idb.put('uploads', {
sessionId, fileFingerprint, fileName, fileSize,
completedChunks: [...done], updatedAt: Date.now()
}, sessionId);
Der Fingerprint ist ein SHA-256 des ersten MB der Datei plus Größe und lastModified — ausreichend, um die Datei nach einem Neuladen wiederzuerkennen. Beim Seitenaufruf prüfen Sie IndexedDB auf Sessions, die weniger als eine Stunde alt sind, und bieten Fortführung an: „Sie haben einen Upload von vor 12 Minuten — fortsetzen?" Starten Sie den Upload nie automatisch ohne Zustimmung — Nutzer laden manchmal bewusst neu, um abzubrechen.
Barrierefreie und Tastatur-freundliche Interaktion
Eine Dropzone, die nur auf Maus-Drag reagiert, versagt bei Screenreader- und Tastaturnutzern. Ergänzen Sie:
role="button"undtabindex="0"auf der Dropzone- Enter/Space-Handler, der den Datei-Input anklickt
aria-live="polite"auf dem Fortschrittsbereich, damit Screenreader Meilensteine ankündigen- Sichtbare Fokus-Stile, nicht nur Hover
- Klare Labels — „Datei hochladen" schlägt „Durchsuchen", das ein bloßes Icon schlägt
Tastatur-Tests sind schnell: Stecken Sie die Maus aus und versuchen Sie 10 Minuten lang, einen Upload abzuschließen. Falls das nicht funktioniert, gilt das ebenso für einen Teil Ihrer Nutzer.
HexaTransfer setzt genau dieses progressive Muster ein — Plain-Form-Fallback, Drag-and-drop-Erweiterung, Fetch-Streaming, IndexedDB-gestütztes Fortführen und spezifische Fehlerbehebung. Testen Sie es unter https://hexatransfer.com — kostenlos, kein Konto erforderlich, bis 10 GB.
Die Details, die Nutzer wirklich wahrnehmen
Das Finish, das mittelmäßige Uploader von hervorragenden trennt, liegt in kleinen Momenten: eine Drop-Animation, die bestätigt, dass die Datei erfasst wurde; ein Fortschrittsbalken, der gleichmäßig steigt statt zu springen; eine ETA, die mit der Zeit präziser wird; spezifische Fehlermeldungen, die sagen, was als nächstes zu tun ist; ein Fortsetzungsangebot nach versehentlichem Neuladen; ein Abschluss-Zustand, der lange genug sichtbar bleibt, um den Freigabe-Link zu kopieren; und ein Abbruch, der den Upload sofort stoppt. Jedes dieser Details sind wenige Zeilen Code — setzen Sie alle um, und Ihr Uploader fühlt sich um eine Größenordnung besser an als die Standard-<input type="file">-Behandlung.
Große Dateien sicher mit Ende-zu-Ende-Verschlüsselung senden
Übertragen Sie Dateien bis zu 10 GB kostenlos mit Ende-zu-Ende-Verschlüsselung. Kein Konto erforderlich. Ihre Dateien werden in Ihrem Browser verschlüsselt, bevor sie hochgeladen werden — niemand sonst kann sie lesen.
Datei senden