Javascript Drag & Drop File-Upload

Javascript Dateien in den Browser laden

Technisch gesehen lädt input type="text" Dateien per Drag & Drop und nicht nur bei einem Klick auf den Button Datei laden. Aber einerseits lässt sich der Button für den File-Upload nicht von CSS überzeugen und andererseits hat der Benutzer dabei keine Chance, die hochzuladenden Dateien noch einmal zu prüfen.

Drag & Drop File

Das Drap & Drop-API unterscheidet sich in vielerlei Hinsicht vom Drag & Drop auf einen input type="file"-Button. Den ersten Unterschied machen die Events der Drag & Drop-Schnittstelle: Die Ereignisse des Drag & Drop für einen File-Upload sind:

dragenter
wenn die Maus / das Zeigegerät die Grenze zur Drop Area überschreitet,
dragover
feuert, wenn die Datei über die Dropzone gezogen wird,
dragleave
wenn die Datei aus der Dropzone gezogen wird,
drop
wenn der Benutzer den Mausbutton losläßt, um die Datei auf die Dropzone fallen zu lassen.

Wenn Elemente innerhalb der Seite zu einem Ziel gezogen werden, entstehen weitere Events, die aber für den File Upload keine Rolle spielen (dragstart, dragend). Das Ziehen der Datei beginnt nicht auf der Seite, sondern außerhalb des Browsers.

FileReader und Drag-Drop

Wenn der Benutzer anhand von Thumbnails sehen kann, welche Dateien auf den Server geladen werden, fühlt er sich sicherer. Die Anzeige von Thumbnails vor dem Datei-Upload ist dank FileReader mit wenigen Zeilen gelöst.

<div id="dropzone">
	<form class="form">
		<p class="dropper">Eine oder mehrere Dateien hierhin ziehen oder mit dem Button laden </p>
		<input type="file" id="fileElem" multiple accept="image/*" onchange="handleFiles(this.files)">
		<label class="button" for="fileElem">Dateien wählen</label>
	</form>
</div>

<div id="preview"></div>

input type="file" muss zwar nicht sein, aber bleibt für Umgebungen, in denen Drag/Drop nicht möglich ist.

Eine oder mehrere Dateien hierhin ziehen oder mit dem Button laden

input type="file" wird versteckt und das label-Element als Button dargestellt. Das schafft einen Rückhalt für die mobilen Browser, auf denen Dateien nicht nicht per Drag und Drop auf die Dropzone gezogen werden können. Der Drag/Drop-Bereich dropzone liegt innerhalb der gestrichelten Box – so wie z.B. auch WordPress einen Upload-Bereich darstellt.

Den Anfang macht das Verhindern der voreingestellten Drag/Drop-Aktionen.

const dropzone = document.getElementById('dropzone');

['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
	dropzone.addEventListener(eventName, preventDefaults, false);
});

function preventDefaults (evt) {
	evt.preventDefault();
	evt.stopPropagation();
}

['dragenter', 'dragover'].forEach(eventName => {
	dropzone.addEventListener(eventName, markzone, false);
});

['dragleave', 'drop'].forEach(eventName => {
	dropzone.addEventListener(eventName, unmarkzone, false);
});

Wenn die Maus über die Dropzone hovert, zeigt der gestrichtelte Rahmen einen farbigen Hinweis, dass hier »Loslassen« angebracht ist. Wenn die Maus den umrandeten Bereich verläßt, wird die Markierung wieder abgestellt.

function markzone(evt) {
	dropzone.classList.add('markzone');
}

function unmarkzone(evt) {
	dropzone.classList.remove('markzone');
}

Wird die Maustaste innerhalb der markierten Zone losgelassen, entsteht das drop-Event und initiiert ein dataTransfer-Objekt. Das DataTransfer-Objekt speichert die Daten, die auf die Dropzone gezogen werden, für die Dauer der Drag-&-Drop-Aktion und kann ein oder mehrere Elemente enthalten.

dropzone.addEventListener('drop', function (evt) {
	let dt = evt.dataTransfer;
	console.log ("dt", dt);
	let files = dt.files;
	handleFiles(files);
});

function handleFiles(files) {
	([...files]).forEach(uploadFile);
	([...files]).forEach(previewFile);
}

▼ dt
  ▶ DataTransfer {dropEffect: "none", 
                  effectAllowed: "all", 
                  items: DataTransferItemList, 
                  types: ["Files"], files: FileList, …}

DataFileItem bietet zwei Funktionen an:

  • getAsString: function
  • getAsFile: function

Datei-Upload mit fetch und FormData

Das FormData-API übermittelt Daten effizienter als der direkte Zugriff auf die Formularelemente. Dateien werden einfach mit append() hinzugefügt.

function uploadFile(file) {
	const url = "upload.php";
	const formData = new FormData()
	formData.append('file', file);

	fetch(url, {
		method: 'POST',
		body: formData
	})
	.then(response => response.json()) // Hier wird die Antwort als JSON interpretiert
	.then(data => {
		if (data.success) {
			console.log(data.message); // Erfolgreich hochgeladen
		} else {
			console.log(data.message); // Fehler beim Hochladen
		}
	})
	.catch(error => {
		console.error('Error:', error); // Fehler beim Fetchen
	});
}

Die Dateien werden in diesem Beispiel an eine PHP-Anwendung auf dem Server übertragen, die mit success (erfolgreich) oder message (z.B. falscher Dateityp) antwortet. catch fängt Fehler bei der Übertragung error ab.

Thumbnails der hochgeladenen Dateien zeigen

Das FileReader-Objekt gibt Javascript lesenden Zugriff auf Dateien und auf Informationen wie Dateiname, Größe und Mimetype.

function previewFile(file) {
	const reader = new FileReader();
	reader.readAsDataURL(file);
	reader.onloadend = function() {
		const img = document.createElement('img');
		img.src = reader.result;
		document.getElementById('preview').appendChild(img);
	}
}

Die Vorschaubilder werden quasi sofort angezeigt, denn der Preview ist eine lokale JavaScript-Aktion.

PHP: Dateien speichern und antworten

Nur ein spartanisches Gerüst ohne Prüfungen und Plausibilitätskontrollen, aber praktisch zum Testen.

<?php
$response = array();

$targetDirectory = "uploads/";

if (!empty($_FILES['file'])) {
    $targetFile = $targetDirectory . basename($_FILES['file']['name']);
    if (move_uploaded_file($_FILES['file']['tmp_name'], $targetFile)) {
        $response['success'] = true;
        $response['message'] = "Die Datei wurde erfolgreich hochgeladen.";
    } else {
        $response['success'] = false;
        $response['message'] = "Beim Hochladen der Datei ist ein Fehler aufgetreten.";
    }
} else {
    $response['success'] = false;
    $response['message'] = "Es wurde keine Datei empfangen.";
}

echo json_encode($response);
?>