HTML-Überschrift anzeigen, dann weitere Daten empfangen funzt nicht
Linuchs
- browser
- javascript
Moin,
immer wieder habe ich sehr umfangreiche Listen / Tables, normalerweise für geschlossenen Benutzergruppen, die die Positionen bearbeiten mit freiem Zugriff per [Strg][F]
Es kann ein paar Sekunden dauern, bevor die Seite aufgebaut ist, aber es ist mir NICHT gelungen, die Überschrift schon mal anzuzeigen.
Früher gab es das PHP-Kommando flush()
das den Inhalt des Ausgabepuffers schon mal abschickte und der Browser zeigte das an. Heute funktioniert entweder flush() nicht mehr oder der Browser (FF) baut sie Seite erst nach Erhalt der gesamten Daten auf.
Problem: Die vorherige Seite bleibt stehen, es sieht so aus, als würde der Seitenwechsel nicht funktionieren. Die Folgeseite wird in Sekunden-Bruchteilen angezeigt (der Seitenwechsel fällt evtl. nicht auf) und wenn sie ähnlich aussieht (weil im anderen Tab ein, zwei Positionen verändert wurden), wartet der Benutzer ...
Plan B: Beim Verlassen einer Seite soll sie verändert werden (z.B. abgedunkelt), das will aber auch nicht funktionieren:
window.addEventListener('DOMContentLoaded', function () {
alert( "Seite geladen" );
...
window.addEventListener('onbeforeunload', function () {
alert( "Seite verlassen" );
});
});
Seite geladen
wird angezeigt, Seite verlassen
aber nicht. Habe den Unterschied zwischen onbeforeunload und beforeunload nicht verstanden und beides ausprobiert.
Gruß Linuchs
Hallo,
Es kann ein paar Sekunden dauern, bevor die Seite aufgebaut ist, aber es ist mir NICHT gelungen, die Überschrift schon mal anzuzeigen.
das kannst du auch nicht gezielt steuern.
Früher gab es das PHP-Kommando
flush()
das den Inhalt des Ausgabepuffers schon mal abschickte und der Browser zeigte das an.
Das gibt's immer noch.
Heute funktioniert entweder flush() nicht mehr oder der Browser (FF) baut sie Seite erst nach Erhalt der gesamten Daten auf.
Da spielen mehrere Dinge mit rein. Zum Beispiel könnte der Webserver die Ausgabedaten nochmal zwischenpuffern, obwohl PHP sagt "hau wech".
Aber vor allem kannst du nicht beeinflussen, wie der Browser mit teilweise empfangenen Daten umgeht. Vermutlich puffert der auch erst ein paar kB, bevor er anfängt, die Seite zu rendern - was ja auch normalerweise vernünftig ist, denn nachfolgende Elemente könnten ja die Größe eines schon angezeigten noch verändern: Eine sehr breite Tabelle bläht beispielsweise die Breite des body-Elements auf; das weiß der Browser aber erst, wenn er die Tabelle zumindest komplett angeschaut hat.
Problem: Die vorherige Seite bleibt stehen
Je nach Browser. Firefox macht das so, ja, während AFAIR der IE den Fensterinhalt löscht.
window.addEventListener('DOMContentLoaded', function () { alert( "Seite geladen" ); ... window.addEventListener('onbeforeunload', function () { alert( "Seite verlassen" ); }); });
Das Event heißt beforeunload (ohne on).
Seite geladen
wird angezeigt,Seite verlassen
aber nicht. Habe den Unterschied zwischen onbeforeunload und beforeunload nicht verstanden und beides ausprobiert.
Der Unterschied wird im Wiki erklärt.
Es kann aber trotzdem sein, dass dieser Eventhandler ignoriert wird, weil es in der Browserkonfiguration so eingestellt ist.
Ciao,
Martin
Hallo Martin,
Es kann aber trotzdem sein, dass dieser Eventhandler ignoriert wird, weil es in der Browserkonfiguration so eingestellt ist.
Okay, dann Plan C:
function verlasseSeite() {
document.getElementsByTagName("body")[0].style.backgroundColor = "rgba( 0,0,0,.5 )";
}
<form action='/' method='post' onsubmit="verlasseSeite()">
...
</form>
Das funktioniert erstmal und hat noch den Vorteil, dass ein anderes Verlassen der Seite, etwa bei Klick auf einen Link (der im neuen Tab erscheint), den Hintergrund nicht verändert.
Der alte Tab wäre auch bei Rückkehr vom anderen Tab (Position bearbeiten) noch dunkel. Gibt es einen Event-Handler für die Rückkehr auf eine vorhandene HTML-Seite?
<body onfocus="alert('Hier bin ich wieder')">
Blendet nach dem Aufbau der Seite brav die Meldung ein und wenn man im Melde-Fenster auf OK klickt, meldet er (der body) sich fröhlich immer wieder ;-)
Wäre nicht übel, wenn manche Seite nach einer Änderung automatisch neu geladen würde. Wenn man (Programmierer) die Rückkehr auf die Seite denn feststellen könnte.
Linuchs
@@Linuchs
document.getElementsByTagName("body")[0]
Das ist unsinnig – in mehrfacher Hinsicht.
Zum einen gibt es im Heuhaufen DOM nur eine Nadel body. Es macht keinen Sinn, nach weiteren Nadeln zu suchen, wenn man schon die eine gefunden hat:
Mit document.querySelector("body")
wäre man schon am Ziel.
Aber auch das ist unsinnig. Es macht keinen Sinn, überhaupt nach Nadeln zu suchen. document.body
existiert bereits.
LLAP 🖖
Hallo Linuchs,
Früher gab es das PHP-Kommando
flush()
das den Inhalt des Ausgabepuffers schon mal abschickte und der Browser zeigte das an. Heute funktioniert entweder flush() nicht mehr oder der Browser (FF) baut sie Seite erst nach Erhalt der gesamten Daten auf.
ich hatte ein ähnliches Problem, konnte es auch lösen, wobei mir immer noch nicht klar ist warum das so ist.
Gruss
Henry
Hallo Henry,
guck mal hier
Ist output_buffering bei Dir vielleicht auf On oder einer Zahl > 0?
Rolf
Hallo Henry,
dein Beispiel funktioniert bei mir (mein Server, mein Browser) mit den gewollten Verzögerungen:
<?php
ob_end_flush();
echo '<h1>test1</h1>';
sleep(1);flush();
echo '<h1>test2</h1>';
sleep(1);flush();
echo '<h1>test3</h1>';
sleep(1);flush();
?>
Merkwürdigerweise funktioniert es mit meinem Konzept nicht. Ich sende eine Mini-HTML-Seite an den Browser und erzeuge dann mit Javascript ein <table>
aus knapp 500 csv-Zeilen, das ich nachträglich einfüge.
Wenn ich die Javascript-Zeit stoppe, um den <table>...</table>
String zu erzeugen, ergeben sich so um die 200 Millisekunden. Das Einfügen benötigt weitere 700 ms:
document.getElementById("adressliste").innerHTML = html_string;
Obwohl die HTML-Seite also komplett beim FF ist, zeigt der nichts an, solange JS läuft.
Mit einem Trick habe ich es dann doch hinbekommen. Die Table-Erzeugung in eine function gepackt und die mit Zeitverzögerung aufgerufen:
//erzeugeTable();
window.setTimeout( erzeugeTable, 100);
Jetzt wird der Kopf der Seite wie gewünscht angezeigt und ca. 2 sec später erscheint die Tabelle. Warum das 2 sec sind und nicht knapp 1 sec JS-Durchlaufzeit, weiß ich nicht. Ach ja, nachdem JS fertig ist, muss der Browser das ja noch umsetzen.
Danke für den Tipp, der mich dazu brachte, noch mal zu experimentieren.
Linuchs
Nachtrag:
Die Wartezeit möchte ich mit der animierten Grafik loading.gif
überbrücken:
Die Animation ist eingefroren, solange JS läuft.
Hallo Linuchs,
das ist beim Fuchs und um die Ecke so. In Chrome läuft sie weiter. Sehr ärgerlich.
Es gibt zwei Möglichkeiten:
Rolf
Hallo Rolf,
- echtes Multitasking: Erzeuge einen Worker. Das ist nur etwas lästig, weil der ein eigenes .js File will und nicht einfach eine Funktion übergeben bekommen kann.
es muss nicht unbedingt ein JS-File sein, das Workerscript kann auch in einem String stehen. Ich mache es z.B. so:
<script id="worker" type="javascript/worker">
"use strict";
var x,y,i;
// ...
</script>
<script>
var workerscript = window.URL.createObjectURL(new Blob([document.getElementById("worker").textContent],{type:'application/javascript'}));
worker = new Worker(workerscript);
// …
</script>
Gruß
Jürgen
Hallo JürgenB,
Worker verlangen Same Origin, und ich habe gelesen, dass ein Worker-Script, das aus einer data-URL geladen wird, nicht von jedem Browser als Same Origin akzeptiert wird.
Dieser StackOverflow Beitrag erzählt eine Menge darüber.
Rolf
Hallo Rolf,
Worker verlangen Same Origin, und ich habe gelesen, dass ein Worker-Script, das aus einer data-URL geladen wird, nicht von jedem Browser als Same Origin akzeptiert wird.
kann ich nicht bestätigen. In allen mir zur Verfügung stehenden Browsern under Windows, MacOs und IOs funktionieren meine Seiten. Und unter „irgendeinem“ Linux habe ich es auch getestet. Ebenso auf einem Android-Smartphone.
Dieser StackOverflow Beitrag erzählt eine Menge darüber.
Die Diskussion ist von 2012. Damals habe ich mich noch nicht mit Workern beschäftigt.
Wenn du Lust hast, kannst du ja auf meiner Site die Unterseite „Logistische Abbildung“ ausprobieren.
Gruß
Jürgen
Hallo Linuchss,
verwende setTimeout(0). Die Callback-Funktion von setTimeout wird definitiv in die Task-Queue gestellt und läuft an, sobald das DOM gerendert ist.
Alternativ geht auf aktuellen Browsern (sprich: Nicht auf dem IE) auch
Promise.resolve().then(erzeugeTable);
Then-Handler werden in die Microtask-Queue gestellt. setTimeout-Callbacks kommen in die Event-Queue und werden von der Event-Loop behandelt.
Makrotasks und Mikrotasks - oder hier wenn Du es zu genau wissen willst...
Rolf
Hallo Rolf,
verwende setTimeout(0)
Prima, wieder 100 ms gespart. Jetzt schraube ich noch mal am JS ...
Linuchs
Hallo Linuchs,
habe gerade selbst noch was in einem Fiddle getestet, mit einem primitiven CSV String und 10000 Rows (sonst ging es zu schnell :) )
Es ist die gleiche Erfahrung wie bei Dir: Zeit X für das Aufbauen des HTML, Zeit Y für das Zuweisen an innerHTML - das löst das Parsen des HTML und das Update des DOM aus, und dann über 10Y für das Darstellen auf dem Bildschirm.
D.h. durch eine Optimierung beim Erzeugen des HTML sparst Du nicht viel. Der Pferdefuß liegt im Rendering. Das kannst Du nur dadurch beschleunigen, dass möglichst wenige dynamiche Komponenten im Rendering liegen. Angeblich soll table-layout: fixed helfen - bei mir hat es das Rendering allerdings um 50% langsamer gemacht. Keine Ahnung wieso.
Das Aufbauen des HTML geht auf 3 Arten.
Der Punkt ist: einen langen String zu verlängern erfordert, ihn zu kopieren. Ein Array zu verlängern erfordert, die Tabelle mit den Objektreferenzen darin zu kopieren (und ggf. die Referenzzähler an den Strings zu manipulieren). Ein festes Array als Zwischenpuffer verhindert beides, und Array.prototype.join kann, bevor es loslegt, erstmal die benötigte Länge für den Zielstring berechnen und den Speicher bereitstellen.
Die gleiche Überlegung kannst Du für die Row-Aufbereitung anstellen und auch da ein festes Array verwenden. Je nach HTML Menge der Rows hilft das mehr oder weniger
Man könnte überlegen, statt einem HTML String ein DocumentFragment aufzubauen. Aber: das ist zum einen inkompatibel zum IE (d.h. du müsstest dafür einen Fallback programmieren oder läufst dort nicht), zum zweiten ist es in meinem Test nicht schneller gewesen (die Übertragung ins DOM ging schneller, aber der Gewinn wurde vom Aufbau des DocumentFragment mehr als aufgefressen) und zum dritten ist der Zeitfresser immer noch das Rendering im Browser, nicht der Update des DOM.
Abgesehen davon hat der Edge-Browser die innerHTML Eigenschaft vor Version 14 nicht unterstützt, aber das sollte heutzutage noch irrelevanter sein als der IE.
Rolf
Hallo Rolf,
vielleicht habe ich dich falsch verstanden, aber ih habe die Erfahrung gemacht, dass innerHTML viel langsamer ist, als DOM-Methoden.
Gruß
Jürgen
Hallo JürgenB,
du hast teilweise recht. Ich hatte gestern einen Fehler in der Zeitmessung; ich hatte die Zeitbasis für den appendChild(frag) falsch und habe die Zeit für die Fragmentgenerierung im appendChild mit drin.
Es kommt aber auch darauf an, ob man beim Aufbau des Fragments die <td> mit textContent oder mit innerHTML füllt. Wenn in den Tabellenzellen inneres HTML verwendet wird, und sei es auch nur ein <br>, muss man innerHTML verwenden, und dann bricht die Fragmentmethode deutlich ein und die HTML-String Technik gewinnt wieder.
Hier mal eine Fiddle-Spielwiese für die drei Varianten: https://jsfiddle.net/Rolf_b/gvLq1r8t/
Die Zeitmessung der Render-Zeit basiert darauf, dass setTimeout einen Task in die Makrotask-Queue stellt und das Rendering eigentlich vorher passieren sollte. Entweder rendert Firefox 70 die Table tatsächlich 3,5 mal schneller als Chrome 79 (Fuchs bei mit 160ms, Chrome ca 550ms), oder da funktioniert was nicht so wie gedacht. Unter Edge ist das Timing noch unkalkulierbarer, da fliegt der setTimeout teils schon nach 4ms, teils nach knapp 1000ms. Von der gefühlten Schwuppdizität her sind es aber die 1000, nicht die 4 🤢
Rolf
Hallo Rolf,
streng nach der Devise „Traue keiner Statistik, die du nicht selbst gefälscht hast“, aber auch weil ich nicht so ganz verstanden habe, was du da alles gemacht hast, hab ich meinen eigenen Vergleich gebastelt.
Unter Chrome und iOs-Safari sind innerHTML und die DOM-Methoden vergleichbar schnell, im FF liegen dazwischen bei großen Tabellen Welten. Die Tabellengröße geht sehr unfreundlich in die Ausführungszeit ein.
Gruß
Jürgen
Hallo JürgenB,
zuerst dachte ich ja, ich spinne. Kein DocumentFragment und trotzdem so schnell?
Aber dann hab ich gesehen dass Du das table-Element erst mit den Rows füllst und dann erst ins lebendige DOM hängst. Das ist dann sozusagen auch ein DocumentFragment.
Deinen DOM-Test (Nr 3) finde ich allerdings etwas unfair. Die Annahme wäre, dass die Tabelleninhalte als HTML-Fragment aus einer DB kommen. Den Aufwand, um einen solchen Inhalt zu parsen, müsste man mit einrechnen - selbst wenn es ein JSON-serialisiertes Objekt ist aus dem Du das Ziel-DOM generierst.
Was deinen innerHTML (Test 1) so langsam macht ist der <output> Container. Es ist aber NUR output, es liegt nicht am inline-Charakter des Elements. Mach mal ein span oder div draus, das verbessert die Schwuppdizität dramatisch und lässt FF wieder an Chrome vorbeiziehen.
Was mich verblüfft, ist das Tempo des Stringaufbaus. Meine Erfahrung vor ein paar Jahren war, dass das Aufbauen langer Strings mit += übel langsam ist. Daher mein Vorschlag mit Teilstrings, die man in einem Array sammelt. Das ist nach wie vor schnell, aber die Strings sind nicht mehr langsamer.
Rolf
Hallo Rolf,
Was deinen innerHTML (Test 1) so langsam macht ist der <output> Container. Es ist aber NUR output, es liegt nicht am inline-Charakter des Elements. Mach mal ein span oder div draus, das verbessert die Schwuppdizität dramatisch und lässt FF wieder an Chrome vorbeiziehen.
das ist ja Wahnsinn. <div>.innerHTML ist mehr als 10mal so schnell wie <output>.innerHTML. Mit div als Container scheint es egal zu sein, wie man die Elemente erzeugt.
Gruß
Jürgen
@@Rolf B
Das Aufbauen des HTML geht auf 3 Arten. […] Der Punkt ist: einen langen String zu verlängern erfordert, ihn zu kopieren.
Ich lese hier immer String, String, String, … Der Punkt ist aber: Das Aufbauen des DOM (darum geht es ja hier) geht auf 2 Arten:
Wenden wir uns doch mal letzterem zu. Wenn man – aus guten Gründen – vermeiden möchte, dass die Rendering-Engine bei jedem Einfügen eines Elements die Seite neu rendert, dann baut man sich erst den Teilbaum zusammen und hängt ihn dann in einem Stück ins DOM: createDocumentFragment()
FTW.
LLAP 🖖
PS: Wie Bäume zusammengebaut werden 🤣
Hallo Gunnar,
Du hast meinen Beitrag aber zu Ende gelesen?
Man könnte überlegen, statt einem HTML String ein DocumentFragment aufzubauen
Rendering "all-in-one" erreicht man durch Zuweisung an innerHTML oder durch Einfügen eines DocumentFragment
s. Ich nehme an, dass der Browser bei Zuweisung an innerHTML intern ein DocumentFragment aufbaut und dann überträgt, denn unter dem Strich sind beide Methoden in etwa gleich schnell, wenn man den String wie von mir beschrieben in Häppchen aufbaut.
Der Löwenanteil geht trotzdem ins Rendering, und an der Stelle fehlt noch der Turbo. Warum hat table-layout: fixed es langsamer statt schneller gemacht?
Mein Fazit kann eigentlich nur sein: Bau keine großen Tabellen, baue statt dessen einen Paging-Mechanismus und zeige immer nur 20-50 Einträge an. Solange alle Sätze am Client vorliegen, kann man ja problemlos und ohne Server-Roundtrip sortieren und filtern.
Rolf
Hallo Rolf,
meine erste Version, eine Grafik im Browser mit Javascript zu erstellen, verwendete span
s als Pixel. Mit innerHTML
war das viel langsamer als mit createElement
und appendChild
. Noch schneller wurde es, wenn mann die span
s gruppiert hat und dann von innen nach außen eingebunden hat, also die fertige Grafik zum Schluss.
Zum Glück gibt es „jetzt“ canvas
und svg
.
Gruß
Jürgen
Moin,
immer wieder habe ich sehr umfangreiche Listen / Tables, normalerweise für geschlossenen Benutzergruppen, die die Positionen bearbeiten mit freiem Zugriff per [Strg][F]
Es kann ein paar Sekunden dauern, bevor die Seite aufgebaut ist, aber es ist mir NICHT gelungen, die Überschrift schon mal anzuzeigen.
Die Tabellen ganz einfach mit AJAX nachladen, hier das Beispiel. MFG