Linuchs: HTML-Überschrift anzeigen, dann weitere Daten empfangen funzt nicht

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

  1. 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

    --
    Sei n die Anzahl der bekannten Fehler in einer Software, dann gilt stets: n = n+1
    1. 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

      1. @@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 🖖

        --
        „Man kann sich halt nicht sicher sein“, sagt der Mann auf der Straße, „dass in einer Gruppe Flüchtlinge nicht auch Arschlöcher sind.“
        „Stimmt wohl“, sagt das Känguru, „aber immerhin kann man sich sicher sein, dass in einer Gruppe Rassisten nur Arschlöcher sind.“

        —Marc-Uwe Kling
  2. 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

    --
    Meine Meinung zu DSGVO & Co:
    „Principiis obsta. Sero medicina parata, cum mala per longas convaluere moras.“
    1. Hallo Henry,

      guck mal hier

      Ist output_buffering bei Dir vielleicht auf On oder einer Zahl > 0?

      Rolf

      --
      sumpsi - posui - clusi
    2. 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

      1. Nachtrag:

        Die Wartezeit möchte ich mit der animierten Grafik loading.gif überbrücken:

        Tabelle wird aufbereitet Loading

        Die Animation ist eingefroren, solange JS läuft.

        1. Hallo Linuchs,

          das ist beim Fuchs und um die Ecke so. In Chrome läuft sie weiter. Sehr ärgerlich.

          Es gibt zwei Möglichkeiten:

          • kooperatives Multitasking: Du baust den String in Schritten auf und zwischen zwei Schritten gibst Du dem Browser eine Chance, eigene Tasks abzuarbeiten. Dazu musst Du deine String-Aufbauschleife unterbrechen, Dir merken, wo Du bist, und mit setTimeout die nächste Runde in die Queue legen. Unter dem Strich wird der Aufbau des Strings dann länger dauern
          • 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.

          Rolf

          --
          sumpsi - posui - clusi
          1. 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

            1. 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

              --
              sumpsi - posui - clusi
              1. 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

      2. 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

        --
        sumpsi - posui - clusi
        1. Hallo Rolf,

          verwende setTimeout(0)

          Prima, wieder 100 ms gespart. Jetzt schraube ich noch mal am JS ...

          Linuchs

          1. 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.

            1. langsam: eine html Variable als String und jede Zelle daran anhängen
            2. schneller: eine html Variable als String, jede Row erstmal in einem eigenen String aufbauen und den, wenn die Row beisammen ist, an die html Variable anhängen
            3. am schnellsten: eine html Variable als Array mit fester Größe. Du weißt, dass Du N Zeilen haben wirst. Initialisiere html = Array(N). Jede Row i als String rendern und an html[i] zuweisen, am Ende dann innerHTML = html.join()

            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

            --
            sumpsi - posui - clusi
            1. 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

              1. 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

                --
                sumpsi - posui - clusi
                1. 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

                  1. 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

                    --
                    sumpsi - posui - clusi
                    1. 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

            2. @@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:

              • HTML-Code als String
              • DOM-Methoden

              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 🤣

              --
              „Man kann sich halt nicht sicher sein“, sagt der Mann auf der Straße, „dass in einer Gruppe Flüchtlinge nicht auch Arschlöcher sind.“
              „Stimmt wohl“, sagt das Känguru, „aber immerhin kann man sich sicher sein, dass in einer Gruppe Rassisten nur Arschlöcher sind.“

              —Marc-Uwe Kling
              1. 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 DocumentFragments. 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

                --
                sumpsi - posui - clusi
                1. Hallo Rolf,

                  meine erste Version, eine Grafik im Browser mit Javascript zu erstellen, verwendete spans als Pixel. Mit innerHTML war das viel langsamer als mit createElement und appendChild. Noch schneller wurde es, wenn mann die spans 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

  3. 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