Signatur
Beschreibung
Mit getAsFileSystemHandle() verbindet man Drag & Drop mit der File System Access API. Zieht der Nutzer eine Datei oder einen Ordner auf die Seite, erhält man statt eines einfachen File-Objekts ein Handle, das dauerhaft auf den Eintrag im Dateisystem zeigt. Über dieses Handle kann man später erneut lesen, mit createWritable() zurückschreiben oder, bei Ordnern, den Inhalt rekursiv durchlaufen.
Die Methode ist nur für Einträge mit kind === "file" sinnvoll. Bei Text-Einträgen (kind === "string") wird das Promise mit null erfüllt. Welches Handle man bekommen hat, zeigt die Eigenschaft handle.kind: Sie ist entweder "file" oder "directory".
Wichtig: Die DataTransferItemList gibt es nur, solange das drop-Ereignis synchron läuft. Deshalb muss man alle Aufrufe von getAsFileSystemHandle() sofort im Handler starten und die Promises sammeln. Erst danach darf man mit await auf sie warten. Wer vor einem weiteren Aufruf ein await setzt, bekommt für die restlichen Einträge keine gültigen Daten mehr.
Die Methode eignet sich besonders für Editoren, Bildbearbeitung oder IDE-ähnliche Web-Apps. Dort sollen gezogene Dateien direkt bearbeitet und gespeichert werden, ohne dass sie neu heruntergeladen werden müssen.
Rückgabewert
Promise, das mit einem FileSystemFileHandle (bei Dateien) oder einem FileSystemDirectoryHandle (bei Ordnern) erfüllt wird. Ist der Eintrag keine Datei (kind !== "file"), wird es mit null erfüllt.Beispiele
Dateien und Ordner per Drop unterscheiden
const dropZone = document.querySelector('#drop-zone');
// dragover abbrechen, damit ein Drop überhaupt möglich ist
dropZone.addEventListener('dragover', (e) => e.preventDefault());
dropZone.addEventListener('drop', async (e) => {
e.preventDefault();
// WICHTIG: alle Promises synchron einsammeln, erst danach awaiten
const promises = [...e.dataTransfer.items]
.filter((item) => item.kind === 'file')
.map((item) => item.getAsFileSystemHandle());
for (const handle of await Promise.all(promises)) {
if (!handle) continue;
if (handle.kind === 'file') {
console.log(`Datei: ${handle.name}`);
} else {
console.log(`Ordner: ${handle.name}`);
}
}
});
Gezogene Textdatei bearbeiten und zurückspeichern
const editor = document.querySelector('#editor');
const saveBtn = document.querySelector('#save');
let fileHandle = null;
editor.addEventListener('dragover', (e) => e.preventDefault());
editor.addEventListener('drop', async (e) => {
e.preventDefault();
const item = e.dataTransfer.items[0];
if (item?.kind !== 'file') return;
const handle = await item.getAsFileSystemHandle();
if (handle?.kind !== 'file') return;
fileHandle = handle;
const file = await fileHandle.getFile();
editor.value = await file.text();
});
saveBtn.addEventListener('click', async () => {
if (!fileHandle) return;
// Schreibrechte anfragen (zeigt ggf. einen Browser-Dialog)
const perm = await fileHandle.requestPermission({ mode: 'readwrite' });
if (perm !== 'granted') return;
const writable = await fileHandle.createWritable();
await writable.write(editor.value);
await writable.close();
console.log(`${fileHandle.name} gespeichert`);
});
Gezogenen Ordner rekursiv auflisten, mit Rückfallebene
const listTree = async (dirHandle, prefix = '') => {
for await (const [name, entry] of dirHandle.entries()) {
console.log(`${prefix}${entry.kind === 'directory' ? '📁' : '📄'} ${name}`);
if (entry.kind === 'directory') await listTree(entry, prefix + ' ');
}
};
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', async (e) => {
e.preventDefault();
const items = [...e.dataTransfer.items].filter((i) => i.kind === 'file');
// Feature-Erkennung: sonst auf getAsFile() ausweichen
if (!('getAsFileSystemHandle' in DataTransferItem.prototype)) {
items.map((i) => i.getAsFile()).forEach((f) => console.log(`📄 ${f.name}`));
return;
}
const handles = await Promise.all(items.map((i) => i.getAsFileSystemHandle()));
for (const h of handles) {
if (h?.kind === 'directory') await listTree(h);
}
});
// Wichtig · Fallstricke
- Browser-Unterstützung: Die Methode gibt es nur in Chromium-Browsern (Chrome und Edge ab 86). Firefox und Safari unterstützen sie nicht. Prüfe deshalb vorher mit
'getAsFileSystemHandle' in DataTransferItem.prototype, ob sie vorhanden ist, und weiche sonst aufgetAsFile()oderwebkitGetAsEntry()aus. - Nur in sicheren Kontexten: Die Methode funktioniert nur über HTTPS oder auf
localhost. - Kein
awaitvor dem Einsammeln: Nach dem erstenawaitistdataTransfer.itemsleer bzw. ungültig. Starte deshalb alle Aufrufe synchron im Handler und warte danach gesammelt mitPromise.all(). - Berechtigungen: Durch den Drop erhält die Seite nur Leserechte. Zum Schreiben musst du mit
requestPermission({ mode: 'readwrite' })anfragen. Dieser Aufruf braucht eine Nutzeraktion, zum Beispiel einen Klick. - Nicht in
dragover/dragenter: Der Zugriff auf die Dateiinhalte ist nur imdrop-Ereignis erlaubt. - Handles lassen sich in IndexedDB speichern und später wieder verwenden. Nach einem Neuladen der Seite müssen die Berechtigungen aber erneut geprüft werden.
- Es gibt die neuere, experimentelle Variante
getAsFileSystemEntry()nicht. Verwechsle die Methode nicht mitwebkitGetAsEntry(), das die ältere File-and-Directory-Entries-API nutzt.