Kein Fortschritt ohne Ladebalken
Kürzlich musste ich für ein Webprojekt bei einem Aufruf eine größere Anzahl an PDF-Dateien erstellen. Da das Ergebnis teilweise aus mehreren hunderte Seiten bestand, hat die Erstellung recht lange gedauert. Hier dem Benutzer einfach nur eine leere Seite anzuzeigen mit der Lade-Animation des Browsers ist nicht optimal. Dort kamen direkt die Rückfragen, ob denn das System arbeitet und wann die Erstellung der PDF-Dateien abgeschlossen ist.
Dementsprechend habe ich mich entschieden, einen entsprechenden Ladebalken in PHP & JavaScript zu programmieren:
Ladebalken mit PHP & JavaScript
Wie ihr wisst, wird der PHP-Script auf dem Server ausgeführt. Sobald dieser seine Arbeit erledigt hat, wird das Ergebnis (das entsprechende HTML) an den Browser gesendet zur Darstellung.
Hier eine Ladeanimation hinzubekommen muss man auf ein paar Befehle in PHP zurück greifen. Ebenfalls ist JavaScript notwendig, um den Ladebalken dann beim Benutzer anzuzeigen.
Hier das vollständige Script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
<?php //doFlush sendet den Inhalt an den Browser function doFlush() { @ob_end_flush(); @ob_flush(); @flush(); } //Unser HTML Grundgerüst für die Ausgabe des Ladebalkens function html_basis() { $basis = <<<EOS <!doctype html> <html lang="de"> <head> <meta charset="utf-8"> <title>Ladebalken</title> <!-- Bootstrap core CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> </head> <body> <div class="container main-container"> <h2>Bitte warten...</h2> <!-- Progress bar holder --> <div id="progress" style="width:100%; height: 22px; border:1px solid #ccc;"></div> <!-- Progress information --> <div id="information" style="margin-top: 5px;"></div> </div> EOS; echo $basis; doFlush(); } //Aktualisiert den Ladeblaken function html_update($prozent, $msg) { echo '<script> document.getElementById("progress").innerHTML="<div style=\"width: '.round($prozent*100,2).'%;background-color:#28a745; height:22px;\"> </div>"; document.getElementById("information").innerHTML="'.$msg.'"; </script>'; doFlush(); } //Sendet den Benutzer zum Schluss zu einer neuen URL function redirect($url, $msg) { echo ' <script> document.getElementById("information").innerHTML="<a href=\"'.$url.'\">» '.$msg.'</a>"; window.location.replace("'.$url.'"); </script> '; doFlush(); } //Ein paar PHP Einstellungen damit die schrittweise Ausgabe funktioniert @ob_implicit_flush(true); @ini_set('zlib.output_compression', 0); @ini_set('implicit_flush', 1); @set_time_limit(0); header('Content-Encoding: none'); //Ausgabe des HTML Grundgerüst html_basis(); // Nun kommt die zeitaufwendige Funktion // beispielsweise das Erstellen von PDF Dateien $anzahl = 15; for($i=0; $i<$anzahl; $i++) { //Immer wenn wir den Ladebalken aktualisieren wollen, rufen wir html_update auf html_update($i/$anzahl, "Erstelle Seite ".($i+1)." von $anzahl"); //Wir nutzen Sleep um die langsame PHP Funktion zu simulieren sleep(1); } //Zum Abschluss leiten wir den Benutzer weiter, z.B. zu der URL um die Datei herunterzuladen redirect("https://www.php-einfach.de", "Dateien erfolgreich erstellt. Du wirst weiter geleitet"); ?> |
Daten auf bereits geladener Seite aktualisieren
Das Script eignet sich, um den Fortschritt in einem PHP-Script auszugeben. Hat man auf einer Seite Daten die sich konstant ändern, z.B. die Aktienkurse oder Bitcoin-Kurse (wie z.B. bei Bitcoin Evolution), dann ist dieser Ansatz der falsche. Hier ist es besser mittels JavaScript eine Server-API anzusprechen, die die neuesten Daten zurück gibt und darstellt. Wer sich für Bitcoins interessiert: Registrieren Sie sich noch heute
Ladebalken im Detail
Hier die einzelnen Schritte noch mal im Detail erklärt.
Bevor der Webserver etwas an den Besucher sendet, puffert er dieses normalerweise bis das PHP-Script komplett fertig gearbeitet hat. Bei einem Ladebalken ist dies aber unerwünscht. Wir wollen unsere Updates noch während das Script arbeitet. Um dies zu realisieren, haben wir eine doFlush Methode:
1 2 3 4 5 6 |
//doFlush sendet den Inhalt an den Browser function doFlush() { @ob_end_flush(); @ob_flush(); @flush(); } |
Ebenfalls versuchen wir das Sendeverhalte noch mittels PHP-Einstellungen entsprechend zu setzen:
1 2 3 4 5 6 |
//Ein paar PHP Einstellungen damit die schrittweise Ausgabe funktioniert @ob_implicit_flush(true); @ini_set('zlib.output_compression', 0); @ini_set('implicit_flush', 1); @set_time_limit(0); header('Content-Encoding: none'); |
Dies sollte bei fast allen Webserver dazu führen, dass Ausgaben des PHP-Scripts nicht erst zwischengespeichert werden, sondern direkt an den Besucher gesendet werden.
Als nächstes definieren wir unser HTML-Grundgerüst mit der Anzeige des Ladebalkens:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
//Unser HTML Grundgerüst für die Ausgabe des Ladebalkens function html_basis() { $basis = <<<EOS <!doctype html> <html lang="de"> <head> <meta charset="utf-8"> <title>Ladebalken</title> <!-- Bootstrap core CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> </head> <body> <div class="container main-container"> <h2>Bitte warten...</h2> <!-- Progress bar holder --> <div id="progress" style="width:100%; height: 22px; border:1px solid #ccc;"></div> <!-- Progress information --> <div id="information" style="margin-top: 5px;"></div> </div> EOS; echo $basis; doFlush(); } |
Immer wenn wir nun eine neue PDF-Seite im PHP-Script erstellt haben, wollen wir eine Info an den Besucher senden, dass das Script fortschritte macht und wie viele Seiten bereits erstellt wurden. Dazu nutzen wir folgende Funktion:
1 2 3 4 5 6 7 8 |
//Aktualisiert den Ladeblaken function html_update($prozent, $msg) { echo '<script> document.getElementById("progress").innerHTML="<div style=\"width: '.round($prozent*100,2).'%;background-color:#28a745; height:22px;\"> </div>"; document.getElementById("information").innerHTML="'.$msg.'"; </script>'; doFlush(); } |
Dies ist ein einfaches JavaScript, dass die breite des Ladebalkens entsprechend anpasst. Ebenfalls aktualisiert es unseren Text, den wir auf der Seite ausgeben.
Diese beiden Funktionen können wir nun für die Darstellung des Ladebalkens verwenden:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Ausgabe des HTML Grundgerüst html_basis(); // Nun kommt die zeitaufwendige Funktion // beispielsweise das Erstellen von PDF Dateien $anzahl = 15; for($i=0; $i<$anzahl; $i++) { //Immer wenn wir den Ladebalken aktualisieren wollen, rufen wir html_update auf html_update($i/$anzahl, "Erstelle Seite ".($i+1)." von $anzahl"); //Wir nutzen Sleep um die langsame PHP Funktion zu simulieren sleep(1); } |
Hier rufen wir zuerst html_basis() auf. Anschließend haben wir eine Schleife um beispeilsweise 15 Seiten zu erstellen. Damit die Schleife nicht unmittelbar durchläuft, nutzen wir noch die sleep-Funktion, die den Programmablauf um 1 Sekunde verzögert.
Wenn die Schleife dann durchgelaufen ist und alle PDF-Seiten erstellt sind, wollen wir unseren Besucher noch weiter schicken, beispielsweise um das PDF anzuschauen oder um es herunterzuladen. Dies erreichen wir mit folgender Funktion:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Sendet den Benutzer zum Schluss zu einer neuen URL function redirect($url, $msg) { echo ' <script> document.getElementById("information").innerHTML="<a href=\"'.$url.'\">» '.$msg.'</a>"; window.location.replace("'.$url.'"); </script> '; doFlush(); } //Zum Abschluss leiten wir den Benutzer weiter, z.B. zu der URL um die Datei herunterzuladen redirect("https://www.php-einfach.de", "Dateien erfolgreich erstellt. Du wirst weiter geleitet"); |
Autor: Nils Reimers