Einfacher Datei-Upload mit Fortschrittsbalken mit Javascript & Ajax

Bei dem Upload von großen Dateien muss der Nutzer oft lange warten, ohne dass er weiß, wie viel Prozent der Dateien schon hochgeladen wurde.

Daher wird gerne auf einen Datei-Upload mit Javascript & Ajax gesetzt. Ein gelungenes Beispiel dafür ist Dropzone.js. Derartige Lösungen haben allerdings einen Nachteil: Als Webmaster müsst ihr die Lösung erst in euer Formular integrieren und ihr müsst eine extra PHP-Seite einrichten, die den Upload verarbeitet.

Ich habe mich gefragt: Geht es noch einfacher? Die Antwort lautet: Ja! In diesem Artikel zeige ich dir, wie du mit geringem Aufwand einen Datei-Upload mit Fortschrittsbalken über JavaScript und Ajax realisieren kannst. Du brauchst keine weitere PHP-Seite für die Verarbeitung des Uploads – nur die Zielseite, die du ohnehin schon verwendest.

Die Lösung für den Datei-Upload

Die Lösung: Wir senden mit AJAX das Formular genau so, wie PHP es an den Server senden würde. Dazu werden alle möglichen Formularinhalte automatisch ausgelesen und an den Server weitergeleitet.

Die Antwort des Servers – also den vollständigen HTML-Code für die komplette Seite – nimmt JavaScript entgegen und blendet ihn nach Abschluss des Uploads ein.

Solange der Upload aber noch läuft, können wir mit JavaScript einen Fortschrittsbalken anzeigen lassen.

Was ist, wenn JavaScript nicht unterstützt wird? Dann wird das Formular ganz normal gesendet. Für euch bedeutet das nachher im PHP-Code, der den Upload verarbeitet, keinen Unterschied.

1. Schritt: JavaScript Code einbinden

Als Erstes bindet ihr dieses JavaScript von mir ein (diesmal mit englischer Kommentierung).

/* Code by Tristan Radtke, https://www-coding.de/einfacher-datei-upload-mit-javascript-ajax/ (can be used for any purposes, but please do not remove this copyright info) */
function ajaxForm(form) {
	// Sends ajax form exactly as not-JS submit would do, shows process bar and at the end it display the answer html as new page
	// @form	contains reference (e.g. given by ajaxForm(this);)
	
	var req = null;
	var formData = null;
	
	req = new XMLHttpRequest();
	formData = new FormData();
	
	if (req == null || formData == null) {
		// Fallback if not supported by browser //
		form.submit();
	}
	
	// Initialize progress info //
	document.body.innerHTML = document.body.innerHTML + '<div style="width:100%;height:100%;position:fixed;top:0;left:0;opacity:0.8;background:#111;" id="uploadProgressOverlay"><div style="margin:50px auto;width:auto;max-width:300px;height:auto;padding:10px;background:#fff;opacity:1;border:1px solid #fff;background:#fff;"><b>Upload läuft.</b><br /><progress value="1" max="100" id="uploadProgress"></progress><div id="uploadPercentInfo">1%</div></div></div>';
	
	// Get all inputs //
	var maxFileSize = null;
	
	for (i = 0; i < form.getElementsByTagName("input").length; i++) {
		if (form.getElementsByTagName("input")[i].type == 'file') {
			// File type //
			if (form.getElementsByTagName("input")[i].multiple) {
				// Beim multiple-Form alle Werte anhängen //
				var elementName = form.getElementsByTagName("input")[i].name;
				
				for (i2 = 0; i2 < form.getElementsByTagName("input")[i].files.length; i2++) {
					if (maxFileSize != null && form.getElementsByTagName("input")[i].files[i2].size > maxFileSize) {
						// ERROR: File is too big //
						document.getElementById('uploadProgressOverlay').parentNode.removeChild(document.getElementById('uploadProgressOverlay'));
						alert('Die Dateien sind zu groß.');
						return false;
					} else {
						// It's ok, append it //
						formData.append(elementName.substr(0, elementName.length - 2)+"["+i2+"]", form.getElementsByTagName("input")[i].files[i2]);
					}
				}
			} else {
				// Just one file //
				if (maxFileSize != null && form.getElementsByTagName("input")[i].files[0].size > maxFileSize) {
					// ERROR: File is too big //
					document.getElementById('uploadProgressOverlay').parentNode.removeChild(document.getElementById('uploadProgressOverlay'));
					alert('Die Dateien sind zu groß.');
					return false;
				} else {
					// It's ok, append it //
					formData.append(form.getElementsByTagName("input")[i].name, form.getElementsByTagName("input")[i].files[0]);
				}
			}
		} else if (form.getElementsByTagName("input")[i].type == 'checkbox') {
			// Add checkbox value only, when checked //
			if (form.getElementsByTagName("input")[i].checked) {
				formData.append(form.getElementsByTagName("input")[i].name, form.getElementsByTagName("input")[i].value);
			}
		} else {
			// Just add value //
			formData.append(form.getElementsByTagName("input")[i].name, form.getElementsByTagName("input")[i].value);
		}
		
		if (form.getElementsByTagName("input")[i].name == 'MAX_FILE_SIZE') {
			// Save max file size for next file element //
			maxFileSize = form.getElementsByTagName("input")[i].value;
		} else if (form.getElementsByTagName("input")[i].type != 'file') {
			// Other element than MAX_FILE_SIZE or file -> delete it //
			maxFileSize = null;
		}
	}
	
	// Get all select fields //
	for (i = 0; i < form.getElementsByTagName("select").length; i++) {
		// Add value //
		formData.append(form.getElementsByTagName("select")[i].name, form.getElementsByTagName("select")[i].value);
	}
	
	// Get all textareas //
	for (i = 0; i < form.getElementsByTagName("textarea").length; i++) {
		// Add value //
		formData.append(form.getElementsByTagName("textarea")[i].name, form.getElementsByTagName("textarea")[i].value);
	}
	
	// EVENTS //
	
	req.onerror = function(e) {
        // Fallback when error //
		form.submit();
    };
    
    req.onload = function(e) {
        // Done -> Overwrite HTML by req.responseText //
    	document.write(req.responseText);
    	document.close();
    };
    
    req.upload.onprogress = function(e) {
    	var progress = e.position || e.loaded;
    	var total = e.totalSize || e.total;
        var percent = Math.round(progress / total * 100);
        
    	document.getElementById("uploadProgress").value = percent;
    	document.getElementById("uploadPercentInfo").innerHTML = percent+"%";
    };
	
	// Send form //
    var formActionURL = form.action;
    
    if (formActionURL == '') {
    	// Take current url //
    	formActionURL = window.location.href; 
    }
    
    req.open("POST", formActionURL);
	req.send(formData);
}

Ihr könnt den Code zum Beispiel in einer .js-Datei speichern und die Datei auf der Website mit dem Datei-Upload einbinden.

2. Schritt: Formular anpassen

Als nächstes müsst ihr euer Formular anpassen. Das geniale an dieser Lösung: Es ist nur eine winzige Änderung notwendig. Fügt dem <form> Element folgende Eigenschaft hinzu:

onsubmit="ajaxForm(this);return false;"

Davon abgesehen sind keine Änderungen notwendig.

Das Upload-Skript unterstützt sogar MAX_FILE_LIMIT und file-Elemente mit multiple Attribut. Das heißt, wenn ihr vor einem file-Element ein hidden-Feld mit den Namen „MAX_FILE_SIZE“ einbaut, wird per JavaScript vor dem Upload geprüft, ob eine Datei zu groß ist. Falls ja, wird der Upload-Vorgang abgebrochen und ein Hinweis angezeigt.

Natürlich könnt ihr das JavaScript auch so auf eure Bedürfnisse anpassen, dass beispielsweise der Prozent-Balken anders aussieht oder die Meldungen anders lauten.

Beispiel gefällig?

Das Skript in Action könnt ihr zum Beispiel beim Massen Bild Editor sehen. Wählt Dateien aus und ladet sie hoch, und schon wird JavaScript die Upload-Arbeit übernehmen.

Screenshot: So sieht der Datei-Upload mit dem Skript und Fortschrittsbalken aus (modifiziert)

Mit diesem Upload-Skript habt ihr also eine einfach umsetzende Lösung für einen Datei-Upload mit JavaScript & AJAX – und vor allem mit Fortschrittsbalken. Viel Erfolg mit dem Skript!

11 Gedanken zu „Einfacher Datei-Upload mit Fortschrittsbalken mit Javascript & Ajax“

  1. Danke für das Script. Das ist genau das was ich gesucht habe. Leider funktioniert es nicht. Was habe ich falsch gemacht?

    Christliches Zentrum Sonnenbühl - Evangelische Freikirche
    <link rel="stylesheet" href="../css/" type="text/css" media="screen">

    <!--

    /* Code by Tristan Radtke, https://www-coding.de/einfacher-datei-upload-mit-javascript-ajax/ (can be used for any purposes, but please do not remove this copyright info) */
    function ajaxForm(form) {
    // Sends ajax form exactly as not-JS submit would do, shows process bar and at the end it display the answer html as new page
    // @form contains reference (e.g. given by ajaxForm(this);)

    var req = null;
    var formData = null;

    req = new XMLHttpRequest();
    formData = new FormData();

    if (req == null || formData == null) {
    // Fallback if not supported by browser //
    form.submit();
    }

    // Initialize progress info //
    document.body.innerHTML = document.body.innerHTML + 'Upload läuft.1%';

    // Get all inputs //
    var maxFileSize = null;

    for (i = 0; i < form.getElementsByTagName("input").length; i++) {
    if (form.getElementsByTagName("input")[i].type == 'file') {
    // File type //
    if (form.getElementsByTagName("input")[i].multiple) {
    // Beim multiple-Form alle Werte anhängen //
    var elementName = form.getElementsByTagName("input")[i].name;

    for (i2 = 0; i2 maxFileSize) {
    // ERROR: File is too big //
    document.getElementById('uploadProgressOverlay').parentNode.removeChild(document.getElementById('uploadProgressOverlay'));
    alert('Die Dateien sind zu groß.');
    return false;
    } else {
    // It's ok, append it //
    formData.append(elementName.substr(0, elementName.length - 2)+"["+i2+"]", form.getElementsByTagName("input")[i].files[i2]);
    }
    }
    } else {
    // Just one file //
    if (maxFileSize != null && form.getElementsByTagName("input")[i].files[0].size > maxFileSize) {
    // ERROR: File is too big //
    document.getElementById('uploadProgressOverlay').parentNode.removeChild(document.getElementById('uploadProgressOverlay'));
    alert('Die Dateien sind zu groß.');
    return false;
    } else {
    // It's ok, append it //
    formData.append(form.getElementsByTagName("input")[i].name, form.getElementsByTagName("input")[i].files[0]);
    }
    }
    } else if (form.getElementsByTagName("input")[i].type == 'checkbox') {
    // Add checkbox value only, when checked //
    if (form.getElementsByTagName("input")[i].checked) {
    formData.append(form.getElementsByTagName("input")[i].name, form.getElementsByTagName("input")[i].value);
    }
    } else {
    // Just add value //
    formData.append(form.getElementsByTagName("input")[i].name, form.getElementsByTagName("input")[i].value);
    }

    if (form.getElementsByTagName("input")[i].name == 'MAX_FILE_SIZE') {
    // Save max file size for next file element //
    maxFileSize = form.getElementsByTagName("input")[i].value;
    } else if (form.getElementsByTagName("input")[i].type != 'file') {
    // Other element than MAX_FILE_SIZE or file -> delete it //
    maxFileSize = null;
    }
    }

    // Get all select fields //
    for (i = 0; i < form.getElementsByTagName("select").length; i++) {
    // Add value //
    formData.append(form.getElementsByTagName("select")[i].name, form.getElementsByTagName("select")[i].value);
    }

    // Get all textareas //
    for (i = 0; i Overwrite HTML by req.responseText //
    document.write(req.responseText);
    document.close();
    };

    req.upload.onprogress = function(e) {
    var progress = e.position || e.loaded;
    var total = e.totalSize || e.total;
    var percent = Math.round(progress / total * 100);

    document.getElementById("uploadProgress").value = percent;
    document.getElementById("uploadPercentInfo").innerHTML = percent+"%";
    };

    // Send form //
    var formActionURL = form.action;

    if (formActionURL == '') {
    // Take current url //
    formActionURL = window.location.href;
    }

    req.open("POST", formActionURL);
    req.send(formData);
    }

    //-->

    Predigt Upload
    Hier können die Predigten hochgeladen werden!

    <?php
    if (isset($submit)) {

    //eingaben der User filtern
    function filtern($text) {
    $text = htmlspecialchars($text, ENT_QUOTES);
    $text = str_replace("+", " ", $text);
    return $text;
    }

    function sonderzeichen_entfernen($str) {
    $search = array("Ä", "Ö", "Ü", "ä", "ö", "ü", "ß", "é", "á", "ó", "&", "§", "%", "+", "#", "$", "*", "`", "´", "'", "=", "~");
    $replace = array("Ae", "Oe", "Ue", "ae", "oe", "ue", "ss", "e", "a", "o", "und", "", "", "", "", "", "", "", "", "", "", "");
    $str = str_replace($search, $replace, $str);
    return $str;
    }

    $fileName = $_FILES["datei"]["name"]; // Ursprünglicher Dateiname
    $fileTmpLoc = $_FILES["datei"]["tmp_name"]; // Temporärer Uploadname
    $fileType = $_FILES["datei"]["type"]; // Der Dateityp
    $fileSize = $_FILES["datei"]["size"]; // Größe der Datei in bytes
    $fileErrorMsg = $_FILES["datei"]["error"]; // Zeigt an ob es während dem Upload Fehler gab (0 = false, 1 = true)
    $datum = date("Y-m-d"); // Das Uploaddatum wird bestimmt!

    // Fehlerüberprüfung und Ausgabe einer Fehlermeldung wenn...
    if (!$fileTmpLoc) { // ...keine Datei gewählt wurde!
    echo 'Upload Fehlgeschlagen: Keine Predigt ausgewählt!Bitte über Durchsuchen... Datei auswählen!';
    } else if($fileSize > 104857600) { // ...die von uns festgelegte Dateigröße überschritten wird!
    echo 'Upload Fehlgeschlagen: Die Predigt darf nicht größer als 100 MB sein!';
    unlink($fileTmpLoc);
    } else if (!preg_match("/.(mp3|mp4)$/i", $fileName) ) { // ...ein unerwümschter Dateityp gewählt wurde!
    echo 'Upload Fehlgeschlagen: Falsches Dateiformat! Bitte mp3 oder mp4 verwenden!';
    unlink($fileTmpLoc);
    } else {

    $fileName = sonderzeichen_entfernen($fileName);

    // Die Datei wird in den richtigen Ordner verschoben!
    move_uploaded_file($fileTmpLoc, "../predigten/$fileName");

    // Es wird überprüft ob die Datei hochgeladen wurde und sich am richtigen Platz befindet!
    if (file_exists("../predigten/$fileName")) {
    echo 'Die Datei '.$fileName.' ('.$fileSize.') wurde erfolgreich hochgeladen!';
    } else {
    echo 'Upload Fehlgeschlagen: Rechte des Uploadordners richtig gesetzt (755)?';
    }

    $db_insert = "INSERT INTO predigtdlczs (datum, sprecher, titel, reihe, datei) VALUES ('".filtern($datum)."', '".filtern($sprecher)."', '".filtern($titel)."', '".filtern($reihe)."', '".filtern($fileName)."')"; // Neue Predigt in die DB-Tabelle "predigtdlczs" eintragen!
    mysql_query($db_insert);
    }

    }
    ?>

    Prediger / Sprecher:  
    Titel der Predigt:  
    Teil der Predigtreihe:  
    Predigt zum Hochladen auswählen:

    © Digital Media Webdesign 2013 - Audio Downloader Version 2.0 (dm-audio_downloader)

    Christliches Zentrum Sonnenbühl - Evangelische Freikirche - Bolbergstr. 22/1, 72820 Sonnenbühl-Willmandingen

    1. Hallo Daniel,

      ich kann dir leider keinen individuellen Support – zumal ohne konkrete Fehlerangabe – geben. Du solltest aber insbesondere darauf achten, den Javascript-Code in script-Tags einzubetten (< script > ohne Leerzeichen).

  2. Hallo Tristan,
    eine sehr gute Ideen mit dem JS. Würde gern die Upload-Lösung in unserem Verein (teilweise kommerziell) einsetzten. Unter welcher Linzenz läuft Deine Lösung? Gibt es eventuell eine Lizenz zum Kaufen?
    Gruß
    Erwin

    1. Hallo Erwin,

      Du darfst den Code auch für kommerzielle Zwecke verwenden – vorausgesetzt die entsprechende Kommentierung (u.a. Urheber-Hinweis „Code by …“) bleibt erhalten.

      Über einen Link – z.B. auch über eine Unterseite deiner Website wie das Impressum – würde ich mich freuen.

      Beste Grüße

  3. Sehr sehr schönes Script – RESPEKT!

    Einzig die teilweise lange Wartezeit nach 100%, in der nix passiert, ist äußerst unschön.

    Eine Idee wie man das weg bekommt?

    1. Lieber Manfred,

      ab 100% übernimmt dein Server. Braucht bspw. das PHP Skript besonders lange, wie bei einer aufwendigen Verarbeitung von Dateien, dauert es leider etwas.

      Du könntest abhängig von der Dateigröße die durchschnittliche Skriptlaufzeit ermitteln und dann zB den Upload von 100 % auf 90 % runterrechnen und die restlichen 10 % (z.B. für insg. 2 Sekunden) laufen dann sobald dein Server übernommen hat.

      Beste Grüße
      Tristan

  4. Hi, tolles Script und funktioniert super.
    Habe nur eine kleine Frage… wie kann ich die Farbe des Statusbalken ändern?

    Danke und Gruss,
    Uwe

      1. Ahh ja… manchmal sieht man vor lauter Bäumen den Wald nicht mehr ;))
        Einfach ein geniales Script!!
        Vielen Dank für die Antwort und weiter so,

        Gruss
        Uwe

  5. Danke für das hilfreiche Skript, dass an sich gut funktioniert.
    Leider habe ich im Anschluss des Uploads folgendes Problem (dass ich ohne das Skript nicht habe):
    Nachdem die Datei hochgeladen wurde, werden mittels PHP Datenbankeinträge vorgenommen und die Datei verschoben. Das PHP-Skript endet mit einer Weiterleitung [header(‚Location:…)]. Die Zielseite wird auch angezeigt, aber im Browser bleibt die alte URL stehen, was dann im Folgenden zu Problemen führt. Lässt sich das Problem lösen?

    1. Lieber Don Tom,

      Benutzt du andere als die üblichen und unterstützten Formularfelder? Du könntest dir mittels var_dump(); einmal mit und einmal ohne Skript die POST-Variable ausgeben lassen um zu schauen, ob es dort zu Abweichungen kommt.