ebody: html2canvas in .js importieren

Hallo,

ich habe ein Script in der Datei js.js mit einer Klasse. Ich möchte html2canvas in das Script js.js importieren.

So funktioniert es leider nicht...

import {html2canvas} from './html2canvas.min.js';

... es erscheint die Fehlermeldung:

Uncaught SyntaxError: The requested module './html2canvas.min.js' does not provide an export named 'html2canvas'.

Wie kann ich das Script auf diesen Weg importieren? Mit npm... kenne ich mich nicht aus.

Eine weitere Frage hätte ich bzgl. der Klasse. Da html2canvas für die Klasse zur Verfügung stehen muss, würde ich die Zeile

import {html2canvas} from './html2canvas.min.js';

in den Constructor der Klasse einbinden. Wenn ich das mache, zeigt aber schon das Syntaxhighlighting (bei mir) schon an, dass diese Schreibweise nicht funktioniert:

class SelectImage{
    constructor(pm) {
        import {html2canvas} from './html2canvas.min.js';
    }
}
  1. Wäre es die richtige Vorgehensweise, html2canvas.min.js in den Constructor einzubinden?
  2. Wenn ja, wie kann man html2canvas.min.js in den Constructor einbinden?

Gruß eboy

  1. Hallo ebody,

    das Script was du hier nennst ist schon gut aber brauchst du den ganzen Ballast oder möchtest du nur Screenshots auf einfache Art damit machen können?

    Gruss
    Henry

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

      Ziel ist es ein DOM Element (Bild + Text) als Bild zu speichern. Das muss nicht zwingend mit html2canvas sein, wenn es eine andere gute Möglichkeit dafür gibt.

      Gruß ebody

      1. Hallo ebody,

        Ziel ist es ein DOM Element (Bild + Text) als Bild zu speichern. Das muss nicht zwingend mit html2canvas sein, wenn es eine andere gute Möglichkeit dafür gibt.

        doch Canvas ist schon gut dafür und auch dein genanntes Script. Nur benötigst du nicht wirklich die Installationen und son github Kram.

        Guckst Du hier.

        Gruss
        Henry

        --
        Meine Meinung zu DSGVO & Co:
        „Principiis obsta. Sero medicina parata, cum mala per longas convaluere moras.“
        1. doch Canvas ist schon gut dafür und auch dein genanntes Script. Nur benötigst du nicht wirklich die Installationen und son github Kram.

          Das ist die html2canvas.min.js die als Script in die HTML Datei eingebunden wird. Aber wie importiert man diese in ein Script wie oben beschrieben?

          Gruß ebody

          1. Hallo ebody,

            Das ist die html2canvas.min.js die als Script in die HTML Datei eingebunden wird. Aber wie importiert man diese in ein Script wie oben beschrieben?

            ähm... Warum willst du die JS-Datei denn so kompliziert einbinden? Du kannst sie als externe JS Datei einbinden wie im Beispiel oder eben kopieren und komplett als <script> einfügen. Verstehe deinen Ansatz nicht.

            Gruss
            Henry

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

              Warum willst du die JS-Datei denn so kompliziert einbinden?

              Wenn seine SelectImage-Klasse selber in einem ECMAScript-Modul steckt, dann ist das der einzig richtige Weg.

              Und kaum macht man es richtig, funktioniert es auch schon - nur ist das vom Autor nicht dokumentiert worden. Sie mein Monsterposting von 15:10 Uhr.

              Rolf

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

                Wenn seine SelectImage-Klasse selber in einem ECMAScript-Modul steckt, dann ist das der einzig richtige Weg.

                ach so, also als Serveranwendung. Hmmm, ob das für diesen Zweck eine gute Idee ist? Würde mich mal interessieren was das Ziel ist, ausser eben Screenshots.

                Gruss
                Henry

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

                  ach so, also als Serveranwendung.

                  Verstehe ich Dich?

                  Module im ECMAScript-Style sind seit Herbst 2017 in Chromium, Edge und Safari, seit Mai 2018 in Firefox und seit 2019 auch in node.js verwendbar (Node v12, Erbium)

                  html2canvas ist ein Tool für den Browser, also wird es eine Clientanwendung sein.

                  Rolf

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

                    ach so, also als Serveranwendung.

                    Verstehe ich Dich?

                    bin jetzt von node.js ausgegangen.

                    Module im ECMAScript-Style sind seit Herbst 2017 in Chromium, Edge und Safari, seit Mai 2018 in Firefox und seit 2019 auch in node.js verwendbar (Node v12, Erbium)

                    html2canvas ist ein Tool für den Browser, also wird es eine Clientanwendung sein.

                    Jain, könnte auch über Server als Clientsimulation nutzbar gemacht werden oder nicht? Aber insgesamt verstehe ich das dann noch weniger. Wo ist dann der Nutzen das so kompliziert zu stricken?

                    Gruss
                    Henry

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

                      welche unnötige Kompliziertheit siehst Du?

                      Die Verwendung von ECMAScript-Modulen an sich?

                      Das Statement

                      import html2canvas from 'html2canvas.esm.js'
                      

                      im nutzenden Modul war etwas schwierig zu gewinnen, was an der schlechten Doku liegt, aber es ist doch nicht komplizierter als

                      <script src="htmlcanvas.min.js">
                      

                      Dafür hat es aber Vorteile im Software Engineering:

                      • die Abhängigkeit zwischen dem SelectImage-Modul und html2canvas ist im SelectImage-Modul sichtbar und muss nicht im HTML erscheinen. Das reduziert vielleicht nicht die Kompliziertheit[1], aber die Komplexität[2].
                      • Der Name html2canvas ist nur dort benutzt, wo er auch gebraucht wird. Binde ich das Script einfach so ein, ohne einen CommonJS/AMD-implementierenden Loader wie require.js, habe ich die html2canvas Funktion im globalen Namensraum herumstehen.

                      Rolf

                      --
                      sumpsi - posui - obstruxi

                      1. wie viele Rädchen drehen sich in einer Kiste ↩︎

                      2. wie viele Kisten muss ich miteinander verschalten ↩︎

            2. ähm... Warum willst du die JS-Datei denn so kompliziert einbinden? Du kannst sie als externe JS Datei einbinden wie im Beispiel oder eben kopieren und komplett als <script> einfügen. Verstehe deinen Ansatz nicht.

              Ich möchte eine Klasse erstellen und möchte diese möglichst einfach gestalten. Der Anwender der Klasse soll nicht selber erst noch html2canvas einbinden müssen. Das soll alles per Klasse geschehen.

              Aber ich könnte html2canvas auch in den Code der index.html auf diese Weise einbinden:

              addHtml2Canvas = document.createElement("script");
              addHtml2Canvas.type = "text/javascript";
              addHtml2Canvas.src = "https://cdn.jsdelivr.net/npm/html2canvas@1.0.0-rc.7/dist/html2canvas.min.js";
              document.body.appendChild(addHtml2Canvas);
              

              Aber die Lösung von @Rolf B scheint zu funktionieren.

              Danke und Gruß ebody

  2. Hallo ebody,

    ich komme mit dieser Importlogik auch nicht wirklich klar. Es scheint aber, als müsstest Du das .esm.js für import verwenden, nur dieses verwendet die Syntax von ECMAScript-Modulen, um einen Export bereitzustellen.

    Exportiert wird die html2canvas-Funktion als default, d.h. du musst

    import html2canvas from 'html2canvas.esm.js'
    

    machen. Zumindest hat das bei mir funktioniert.

    Die "Normalversion" des Scripts, ohne .esm, hat eine Funktion "factory", die die html2canvas-Funktion zurückgibt. Wo die dann landet, dafür werden 3 Fälle unterschieden:

    1. es gibt ein Objekt exports und eine Variable module, die nicht auf undefined steht. In dem Fall wird module.exports auf das Ergebnis der Factory gesetzt.

    2. Es gibt eine Funktion define und diese Funktion hat ein Property namens amd. In diesem Fall wird die Factory-Funktion an define übergeben.

    3. Falls es ein globales this gibt, wird dies als globaler Kontext verwendet. Wenn nicht, wird der Inhalt von self als globaler Kontext verwendet. Und dann wird im globalen Kontext das Ergebnis der Factory als html2canvas abgelegt.

    Was zum grundgütigen Geier bedeutet das?

    (1) ist ein Pattern, dass meines Wissens nur in node.js funktioniert. Aber html2canvas in einem node.js Server laufen zu lassen, ergibt vermutlich keinerlei Sinn. Entweder gibt es browsertaugliche Modul-Lader, die sich so wie node.js verhalten, oder der liebe Niklas aus L.A. hat diese Präambel einfach irgendwoher kopiert. Das "irgendwoher" kann aber auch ein Tool sein, das ECMAScript-Module rückwärtskompatibel macht, und dieses Tool setzt diese Universal-Präambel blindlings ein.

    (2) ist das Pattern eines common.js Modul-Laders wie z.B. require.js. Ein solcher Modul-Lader bietet 2 globale Funktionen an, require und define, und mit define macht man Module bekannt, die andere mit require erhalten können. Wenn ich foo.js lade und darin define(function()...) aufrufe, wird im Modul-Lader ein Modul namens 'foo' erzeugt. Wie auch immer diese Magie funktioniert...

    (3) ist der Default und erzeugt einfach eine globale Funktion.

    Nichts von den dreien ist für ECMAScript-Module geeignet.

    TL;DR: Verwende die .esm.js Version.

    Wenn Du html2canvas nicht global verwenden willst, sondern es erst im Konstruktor importieren möchtest, kannst Du die "function style" Version vom import nutzen. Die arbeitet allerdings asynchron - muss sie tun, weil ja eine externe Ressource geladen wird und das Script darauf nicht warten darf - und macht erstmal nur leere Versprechungen:

    class SelectImage{
        constructor(pm) {
            import('/html2canvas/html2canvas.esm.js')
            .then(module => {
                this.html2canvas = module.default;
            });
        }
    }
    

    Das wird Dir aber vermutlich nicht schmecken, denn Du kannst nun nicht hergehen und dies hier tun:

    let selector = new SelectImage(pm);
    selector.capture();   // FAIL
    

    Wenn die capture-Methode html2canvas verwenden will, schlägt das fehl. Diese Eigenschaft wird erst gesetzt, wenn das Modul geladen und das Promise erfüllt ist.

    Was zum Teufel ist ein Promise?

    Du bräuchtest in SelectImage also noch einen Mechanismus, mit dem Du das erfüllte Promise der import-Funktion an den Nutzer bekannt geben kannst. Zum Beispiel so:

    class SelectImage{
        constructor(pm) {
            return import('/html2canvas/html2canvas.esm.js')
                   .then(module => {
                       this.html2canvas = module.default;
                       return this;
                   });
        }
    }
    
    new SelectImage(pm)
    .then(selector => {
       selector.capture();
    });
    

    Wenn ein Konstruktor ein Objekt explizit zurückgibt, dann wird das zum Ergebnis des new. In diesem Fall ist es das von .then zurückgegebene Folge-Promise, dessen Wert das erzeugte Objekt ist. Und darauf kannst Du einen eigenen .then-Handler legen. Das ist allerdings ein Programmiermuster, bei dem sich dem unschuldigen Erstleser erstmal die Haare sträuben.

    Das Laden mit der import-Funktion ist, denke ich, aber auch nachteilig. Sie verzögert das Laden von html2canvas so lange, bis das Modul wirklich gebraucht wird, und muss dann erstmal warten, bis es verfügbar ist. Das import-Statement sorgt dafür, dass das importierte Modul in dem Moment bestellt wird, wo das Importeur-Modul geladen ist. Der Browser wartet nicht darauf, dass der Code darin verwendet wird. Es könnte sogar sein, dass zuerst mal alle importierten Module geladen werden, bevor die erste Zeile Code des Importeurs losläuft.

    Wenn deine Klasse SelectImage selbst in einem ECMAScript-Modul steckt, brauchst Du vor globalen Variablen, in denen importierte Module stecken, aber keine Angst zu haben. Die sind nur im Modul global, und außerhalb des Moduls nicht sichtbar. Das ist okay.

    Rolf

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

      vielen Dank für die vielen Infos und deine Mühe. Ich gebe zu, dass einige Fragezeichen über meinem Kopf kreisen, weil ich vieles davon zum ersten mal lese. Ich werde es jetzt ausprobieren.

      Danke und Gruß ebody