var: Resize von canvas-Element abhängig von parent-Element

Hallo miteinander!

Bevor mich die "Besonderheiten" des <canvas> Elements noch endgültig in den Wahnsinn treiben, wende ich mich mal an euch mit meinem Problem, bei dem die Ausgangslage wiefolgt ist:

Ein <canvas> Element ist in ein Elternelement eingebettet, welches eine initiale height und width von 500x500px sowie eine min-height und min-width von 500x500px hat und welches mittels resize: both über dieses Minimum hinaus frei skaliert, sowie auch per drag-and-drop frei positioniert werden kann.

Das <canvas> Element selbst sollte stets eine Höhe haben von 100% der Höhe des Elternelments minus 85px und eine Breite von 100% des Elternelements minus 20px.

Wäre das CANVAS also ein "normales" Element sollte das in etwa so aussehen:

#parent {  
  
  min-width: 500px;  
  min-height: 500px;  
  width: 500px;  
  height: 500px;  
  resize: both  
  
}  
  
#parent > canvas {  
  
  min-width: 480px;  
  min-height: 415px;  
  width: calc(100% - 20px);  
  height: calc(100% - 85px)  
  
}

Aber das funktioniert natürlich nicht, weshalb die Höhen - und Breitenangabe für das CANVAS zunächst mal so aussieht: canvas.width = 480; und canvas.height = 415;

Das 'resize'-Event ist zwar eine sehr nützliche Sache, wenn man die Größe des CANVAS relativ zum Browserfenster skalieren will (zumindest über Umwege), aber auf ein "resize" des Elternelementes reagiert es meines Wissens nach nicht...

Die Frage ist also, was ist die beste Möglichkeit, das angestrebte Ziel zu erreichen? :-/

PS: Dass der CANVAS-Kontext bei resize zurückgesetzt wird ist mir klar, aber das ist hier unerheblich, da hier wohl ohnehin nur eine JS-Lösung in Betracht kommt und ich dann dementsprechend die DRAW-Funktion über requestAnimationFrame neu aufrufen würde...

Gruß

var

  1. Hakuna matata!

    Das <canvas> Element selbst sollte stets eine Höhe haben von 100% der Höhe des Elternelments minus 85px und eine Breite von 100% des Elternelements minus 20px.

    Wäre das CANVAS also ein "normales" Element sollte das in etwa so aussehen:

    Also bei mir funktioniert das so, wie du erwarten würdest: http://jsfiddle.net/pox2cu3v/

    --
    “All right, then, I'll go to hell.” – Huck Finn
    1. Hakuna matata!

      Hallo 1UnitedPower!

      Also bei mir funktioniert das so, wie du erwarten würdest: http://jsfiddle.net/pox2cu3v/

      Hehe, ja, solange die Leinwand weiß ist. ;-)

      Aber bedenkte: canvas.width/height != canvas.style.width/height.

      Es müssen die Werte für die Attribute width und height verändert werden, keine style-Eigenschaften, sonst bleibt die Auflösung bei den für jedes CANVAS initial angesetzten 300 x 150px, und diese 300 x 150px werden dann nur auf den per CSS gesetzten Wert gestretcht.

      Gruß

      var

      1. Hakuna matata!

        Also bei mir funktioniert das so, wie du erwarten würdest: http://jsfiddle.net/pox2cu3v/

        Hehe, ja, solange die Leinwand weiß ist. ;-)

        Aber bedenkte: canvas.width/height != canvas.style.width/height.

        Zumindest laut Standard sind die CSS-Eigenschaften ausschlaggebend für die Bitmap-Dimensionen des canvas'. Ich zitiere:

        A canvas element can be sized arbitrarily by a style sheet, its bitmap is then subject to the 'object-fit' CSS property. [CSSIMAGES]

        Und object-fit ist standardmäßig fill.

        Es müssen die Werte für die Attribute width und height verändert werden, keine style-Eigenschaften, sonst bleibt die Auflösung bei den für jedes CANVAS initial angesetzten 300 x 150px, und diese 300 x 150px werden dann nur auf den per CSS gesetzten Wert gestretcht.

        Nach Standard ist dem eben nicht so. Aber du kannst nicht erwarten, dass beim Vergrößern des <canvas> automatisch etwas neu gezeichnet wird. Bereiche die vor der Vergrößerung nicht bemalt waren, sind auch danach unbemalt, bis du etwas darauf kritzelst.

        --
        “All right, then, I'll go to hell.” – Huck Finn
        1. Hakuna matata!

          Hallo!

          [...]Nach Standard ist dem eben nicht so. Aber du kannst nicht erwarten, dass beim Vergrößern des <canvas> automatisch etwas neu gezeichnet wird. Bereiche die vor der Vergrößerung nicht bemalt waren, sind auch danach unbemalt, bis du etwas darauf kritzelst.

          Das ist doch dann genau der Punkt. ;-)

          Selbst wenn dem so wäre, wie du sagst: Wenn ich (über requestAnimationFrame) meine draw-function aufrufe, muss ich (zumindest in WebGL) für das renderingContextObject den viewport festlegen, in der Regel also...

          var ctx = WebGLRenderingContextObject;  
            
          ctx.viewportWidth = canvas.width;  
          ctx.viewportHeight = canvas.height;  
            
          ctx.viewport(0, 0, ctx.viewportWidth, ctx.viewportHeight);
          

          ...wenn die volle Größe des canvas bemalt werden soll, und das funktioniert nur mit Pixelangaben in Ganzzahlen, nicht mit sowas wie calc(100% - 20px), weshalb ich hier nicht schreiben kann ctx.viewportWidth = canvas.style.width.

          Und der Wert für das Elementattribut width bzw. height wird durch die Angabe in CSS gerade mal gar nicht tangiert. Wenn ich es in CSS so schreibe wie im Beispiel und für canvas.width/height keine Angaben mache, bleibt canvas.width = 300 und canvas.height = 150 bestehen. Da findet keine automatische Anpassung statt.

          Gruß

          var

          1. Hakuna matata!

            ...wenn die volle Größe des canvas bemalt werden soll, und das funktioniert nur mit Pixelangaben in Ganzzahlen, nicht mit sowas wie calc(100% - 20px), weshalb ich hier nicht schreiben kann ctx.viewportWidth = canvas.style.width.

            Ich verstehe dein eigentliches Problem immernoch nicht. Aber canvas.style.width ist auf jeden Fall auch eins deiner Probleme: die .style-Eigenschaft ist geeignet um Styles zu setzen, zum Lesen benutzt man getComputedStyle():

            var canvas = document.querySelector('canvas');  
            getComputedStyle( canvas )['width']
            
            --
            “All right, then, I'll go to hell.” – Huck Finn
            1. Hakuna matata!

              Hallo.

              Ok. Verrückt, getComputedStyle( ) funktioniert zwar tatsächlich in dem Sinne, dass der Context sich dynamisch anpasst, aber dafür ist der viewport völlig falsch skaliert, überhaupt nicht den Werten aus getComputedStyle( ) entsprechend, die ich mir parallel anzeigen lasse.

              Kann aber auch an meinem Code liegen, werd das mal checken.

              Jedenfalls danke für den Tip!

              Gruß

              var.

            2. Ahoi.

              Also ich hab mir das nochmal angeguckt: Für getComputedStyle(canvas)['width'] / ['height'] werden zwar tatsächlich die Werte (in px) ausgegeben, die der Eingabe calc(100% - 20px) in CSS entsprechen, aber das ändert nichts daran, dass der context auf Grundlage der Werte berechnet wird, die in den Elementattributen bestimmt sind, und dass sind, unverändert, die initialen 300 x 150px. Es tritt also im Wesentlichen wieder genau der selbe Effekt auf wie von mir eingangs beschrieben: Das Canvas wird auf die in CSS bestimmte Größe gezogen, aber dabei verändert sich nicht die 'Auflösung'.

              Aber du hast recht, ich werde mal versuchen ein live-Beispiel zu erstellen, auch wenn mit dessen Fertigstellung gegebenenfalls erst morgen Vormittag zu rechnen ist. ;-)

              Das sollte etwas Klarheit bringen.

              Nochmals Dank und Gruß

              var

            3. Heja.

              Also, hier mal ein Beispiel, wie das im Idealfall aussehen sollte.

              Soweit ich das verstanden habe, _ersetzt_ die Angabe in CSS die direkte Attributangaben nicht!

              Wenn man canvas.width und canvas.height komplett ignoriert und einfach über CSS die Höhe und Breite bestimmt, dann funktioniert das nicht (richtig), jedenfalls nicht mit dem WebGL-Context, wie man hier gut sehen kann.

              Was aber funktioniert, ist wenn man die Werte aus der CSS Angabe nimmt und damit die Werte für canvas.width und canvas.height _ebenso_ aktualisiert wie den viewport!

              Insofern: Problem gelöst. ;-)

              Nochmal Danke!

              Gruß

              var

              1. Hakuna matata!

                Also, hier mal ein Beispiel, wie das im Idealfall aussehen sollte.

                Soweit ich das verstanden habe, _ersetzt_ die Angabe in CSS die direkte Attributangaben nicht!

                Ersetzt wird da sowieso nichts, aber die Attribute refklektieren nicht die CSS-Dimensionen, das ist richtig. Wenn ich den Standard richtig verstehe (und da hab ich mir nicht sicher), dann sollte das aber nicht weiter tragisch sein, weil die internen Bitmap-Dimensionen direkt von den CSS-Dimensionen und nicht von den width- und height-Attributen bestimmt werden sollten.

                Um die Verwirrung noch ein wenig zu vergrößern: Der WebGL-Viewport skaliert nicht automatisch mit den internen Bitmap-Dimensionen des <canvas>-Elements, sondern will immer manuell angepasst werden.

                Wir reden also von insgesamt vier Koordinatensystemen:

                1. width- und height-Eigenschaften auf dem <canvas>
                2. Die CSS-Dimensionen (getComputedStyle())
                3. die interne Bitmap-Dimension des <canvas>
                4. der Viewport des WebGL-Kontextes

                (2) und (3) sollten eigentlich identisch sein. (1) sollte keine große Rolle spielen. Bei einer Größenveränderung muss (4) auf (2) gesetzt werden. Soweit die Theorie. In der Praxis scheint es aber so zu sein, dass man zusätzlich auch noch (1) auf (2) setzen muss.

                Die Standards könnten hier ruhig etwas ausführlicher gestaltet sein.

                Insofern: Problem gelöst. ;-)

                Immerhin *seufz*

                --
                “All right, then, I'll go to hell.” – Huck Finn
                1. Hakuna matata!

                  Heja!

                  Die Standards könnten hier ruhig etwas ausführlicher gestaltet sein.

                  Dem würde ich definitiv nicht widersprechen wollen, zumal mich deren Lektüre schon das ein oder andere Mal ratlos zurückgelassen hat.

                  Insofern: Problem gelöst.

                  Immerhin *seufz*

                  Trial and error: Im Kontext ;-) von WebGL leider im Moment noch zuoft die erfolgversprechendste Problemlösungsmethode...

                  Gruß

                  var

      2. Hakuna matata!

        Also bei mir funktioniert das so, wie du erwarten würdest: http://jsfiddle.net/pox2cu3v/

        Hehe, ja, solange die Leinwand weiß ist. ;-)

        Und auch, wenn man sie einfärbt:
        http://jsfiddle.net/1wv4nbq9/

        Ich werde das Gefühl nicht los, dass wir einfach aneinander vorbei reden. Bastle du doch mal ein Online-Beispiel und erkläre daran, was du erreichen möchtest, und was du stattdessen bekommst.

        --
        “All right, then, I'll go to hell.” – Huck Finn
  2. Hallo var,

    ich habe das Problem so gelöst, dass ich per JS die Größe des Umgebenden DIVs auslese und die Canvasgröße dann entsprechend setze. Ich gehe sogar so weit, dass ich das Canvas-Element per JS erzeuge.

    Für das umgebende DIV habe ich mir dann ein eigenes "onresize" gebastelt, indem ich per setInterval 5 mal pro Sekunde die Größe messe, und bei Änderung das Canvas-Element einschließlich Inhalt neu erstelle.

    Gruß, Jürgen

    1. Hallo var

      Hallo Jürgen

      ich habe das Problem so gelöst, dass ich per JS die Größe des Umgebenden DIVs auslese und die Canvasgröße dann entsprechend setze. Ich gehe sogar so weit, dass ich das Canvas-Element per JS erzeuge.

      Für das umgebende DIV habe ich mir dann ein eigenes "onresize" gebastelt, indem ich per setInterval 5 mal pro Sekunde die Größe messe, und bei Änderung das Canvas-Element einschließlich Inhalt neu erstelle.

      Lustig, dass du's sagst, denn das ist so ziemlich genau das, was ich bislang tue. ;-)

      Aber warum das Canvas immer neu erstellen?

      setInterval(function ( ) {  
        
        var x = parent.style.width.slice(0, parent.style.width.length - 2) - 20;  
        var y = parent.style.height.slice(0, parent.style.height.length - 2) - 85;  
        
        canvas.width = x;  
        canvas.height = y;  
        
      }, 180);
      

      Lässt du das eigentlich durchlaufen oder hast du das auch noch an ein parent.onmousedown bzw. parent.onmouseup Event geknüpft? - Also setInterval( ) + clearInterval( ).

      Solange das parentElement nicht angeclickt wird, wird sich dessen Größe ja wohl kaum ändern...

      Naja, ich hatte mir halt erhofft, dass es eine elegantere Methode gibt, am besten ohne setInterval( ). :-/

      Gruß

      var

      1. Hallo var,

        Aber warum das Canvas immer neu erstellen?

        Das Canvas wird in (m)einer Grafikbibliothek erstellt, und die kennt nur Löschen und Erstellen. Resize habe ich (noch) nicht vorgesehen.

        Lässt du das eigentlich durchlaufen oder hast du das auch noch an ein parent.onmousedown bzw. parent.onmouseup Event geknüpft? - Also setInterval( ) + clearInterval( ).

        bei mir kann sich die Canvasgröße auch ändern, wenn sich die Größe des Browserfensters ändert, daher läuft die Abfrage immer.

        Gruß, Jürgen