webity: Einem neu erzeugtem Button eine bestimmte Position zuweisen

Hallo zusammen,

ich versuche seit längerem, Buttons mit javascript und

document.createElement("button")

zu erzeugen. Das funktioniert auch soweit. Nur ich möchte den buttons eine bestimmte Position zuweisen. Dazu gibt es

button.style.left und button.style.top

Leider funktioniert das nicht. Wo liegt das Problem? Liegt das daran, das der button noch nicht erzeugt wurde?

Hier mal die Funktion in der das stattfinden soll

let buttonsOutput = document.getElementById("klick_flaechen");

for(let a = 1; a <= 5; a++)
{

    let button = document.createElement("button");

    button.innerHTML = a;
    button.className = "klick_flaeche_buttons";
    button.value = a;
    button.style.left = (a * 70 + "px").toString;
    button.style.top = "200px";
    buttonsOutput.appendChild(button);
 
}

gruß Lars

  1. @@webity

    button.style.left und button.style.top

    Leider funktioniert das nicht. Wo liegt das Problem?

    Dass left und top nur auf positionierte Elemente wirken, hast du auf dem Schirm? [CSS2.1 §9.3.2]

    Aber das ist wohl an sich schon keine gute Idee, das so zu machen. Responsiv ist das nicht. Wie sieht das auf kleinen Viewports (bspw. Smartphones) aus?

    Was soll das werden? Vermutlich gibt es bessere Lösungen dafür.

        button.style.left = (a * 70 + "px").toString;
    

    Hier steckt ein Fehler. .toString liefert die Funktion, nicht den Funktionswert. Wenn du den haben will, musst du .toString() schreiben.

    Brauchst du aber nicht. Durch die Stringkonkatenation (+ mit einem String) ist der Klammerausdruck bereits ein String; .toString() ist überflüssig.

    Und mit Stringkonkatenation würde ich das auch nicht machen. Dafür gibt es template literals:

        button.style.left = `${a * 70}px`;
    

    🖖 Живіть довго і процвітайте

    --
    Ad astra per aspera
  2. Hallo webity-Lars,

    ich würde noch anmerken wollen, dass innerHTML nur verwendet werden sollte, wenn Du wirklich HTML in den Button setzen willst. Für Text nimm textContent.

    Du hast ja eh schon einen Container klick_flaechen. Eigentlich sollte es dann genügen, wenn Du die Buttons unpositioniert in diesen Container einfügst und ihre Größe und Lage darin mit CSS steuerst. Eine Klasse klick_flaeche_buttons brauchst Du dann nicht (die ohnehin eigentlich klick_flaeche_button heißen müsste).

    Dein klick_flaechen Container könnte beispielsweise eine Flexbox sein:

    #klick_flaechen {
       display: flex;
       gap: 1em;
    }
    #klick_flaechen button {
       width: 5em;
    }
    

    Statt mit width kannst Du auch die flex-basis und flex-grow Eigenschaften verwenden, damit sich die Buttons an die Containergröße anpassen.

    Für mehr Tipps müsste man deine Seite live erleben…

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hallo ihr beiden,

      erstmal Danke für eure Antworten. Ja ich bin noch Anfänger. Ich gehe gerade ein Javascript - Online Kurs durch, bei dem gerade das DOM erklärt wird. Daher meine leichtsinnge Annahme, mal eben ein paar button zu erzeugen und zu plazieren.

      Also ich möchte ein Spiel programmieren, bei dem der Player nach einer bestimmten vorgegebenen Reihenfolge auf button klickt, die kreisförmig angeordnet sind. Und die Anzahl der button kann auch varieren

      Mit Div habe ich das hinbekommen, aber ich habe eine bestimmte Anzahl an Divs auf der Webseite erstellt und die überflüssigen unsichtbar gemacht. War keine tolle Lösung.

      Hier mal die 1. Version (Spiel starten / beenden ohne Funktion)

      https://jsfiddle.net/L29fbcap/

      So wie die divs möchte ich gerne nur so viel wie nötig an button erzeugen und positionieren. Wie gehe ich da am besten vor?

      Und hier mein 2. Versuch mit button erzeugen:

      https://jsfiddle.net/gLpu314j/

      Vielleicht könnt ihr mich jetzt in die richtige Richtung schubsen!

      Gruß Lars

      1. Hallo Lars,

        Buttons im Kreis? Irgendwie klingelt mir da ein Deja-Vu... und es war nicht deim Besuch im Mai diesen Jahres.

        Entweder machst Du den gleichen Kurs wie jemand anderes, der schon mal hier war, oder Du bist im Onlinekurs sitzengeblieben und wiederholst.

        Ein paar Tips gibt's noch, aber dafür muss ich an den PC.

        Rolf

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

          also Respekt! Gutes Gedächtnis. Ich hatte schon mal so eine Frage, aber mit divs.... Da sollte ich mein Konzept noch mal überdenken. Damals war es ein HTML / CSS Kurs. Jetzt ist es ein Javascript Kurs.

          Ich weiß nicht, wieviele Kurse ich noch machen muss, um das Spiel fertig zu stellen!😆

          Gruß Lars

          1. Hallo Lars,

            gut, dann mal hier ein paar Tipps basierend auf deinem Code.

            • 13 Radiobuttons für eine Wertauswahl sind keine gute Idee. Nimm statt dessen

              • ein Eingabefeld input type="number" (das liefert Dir Up/Down-Buttons)
              • ein input type="range". Beim range-Input brauchst Du ggf. noch einen Handler für das input-Event, der Dir den aktuellen Wert im Klartext anzeigt, das tut das range-Control nicht von allein.
              • ein select Element
            • Um aus 13 Radiobuttons den herauszufischen, der selektiert ist, brauchst Du nicht jeden einzeln anzufassen. Verwende die :checked-Pseudoklasse, um den markierten zu finden. Aber - wenn Du auf die Horde von Radiobuttons verzichtest, stellt sich das Problem gar nicht.

               let selectedRadio = document.querySelector("input[name=anzahlDerKreise]:checked");
               return selectedRadio ? selectedRadio.valueAsNumber : 0;
            

            Den Operator ?: kennst Du?

            • Deine Funktion posCalculate gibt die berechnete Position zurück. Du übernimmst sie beim Aufruf aber nicht, sondern verwendest eine Objekteigenschaft, in der die Position „zwischengeparkt“ wird. Das ist - sorry - bäh. Um erstmal nicht zu weit von deinem Code abzuweichen - das ginge so besser:
            posCalculate(degree) {
               let result;            // lokale Variable statt this-Eigenschaft
               ...
               result = {             // Position als Objektliteral erzeugen und zuweisen
                  x: ...;             // Aber einfach nur x und y verwenden
                  y: ...;
               };
               ...
               return position;
            }
            
            kleine_flaechen_erzeugen_positionieren() {
               ...
               let position = posCalculate(degree);
               button.style.left = `${position.x}px`;
               button.style.top  = `${position.y}px`;
               ...
            }
            

            Es ist zu viel Schreibarbeit, die Eigenschaften x_position und y_position zu nennen. Das Objekt stellt eine Position dar, also einfach nur x und y. Bei der Zuweisung an style.left und style.top hattest Du zu viel alten Kram stehen gelassen. Wenn Du `${foo} + px` schreibst, wird + px genau so ins Ergebnis gestellt. In einem Template-String (also dieses Ding in Backticks) wird eine ${...} Gruppe unmittelbar durch ihren Wert ersetzt. Ein + ist nicht mehr nötig. Ein .toString() auch nicht, das Ergebnis ist bereits ein String.

            Ansonsten passieren da einige merkwürdige Dinge, die ich nicht verstehe. Vor allem nicht die Abfrage auf den Winkelquadranten in posCalculate, sowas sollte komplett überflüssig sein. Ich bastele das jetzt aber nicht irgendwie hin, bislang sind da noch zu viele sonstige Fehler.

            Rolf

            --
            sumpsi - posui - obstruxi
      2. @@webity

        Ja ich bin noch Anfänger. Ich gehe gerade ein Javascript - Online Kurs durch, bei dem gerade das DOM erklärt wird.

        Du lernst JavaScript vor CSS?

        Dabei kommt dann raus, Dinge mit JavaScript umzusetzen, die überhaupt nicht mit JavaScript gemacht werden sollten: Layout-Sachen, die mit CSS gemacht werden können. Und sollten!

        Dass mit JavaScript auf alles geballert wird, was bei drei nicht auf’m Baum ist, ist ein Grundübel heutiger Webentwicklung. Viele, die sich „Frontend-Entwickler“ nennen, können zwar JavaScript, ihnen mangelt es aber an CSS-Kenntnissen. 😫

        Um Elemente im Kreis anzuordnen, braucht man genau soviel JavaScript:

        Ja, richtig gelesen: gar keins. ☞ button circle

        (Das Beispiel ist für bis zu einem Dutzend Buttons gemacht. Wenn’s mehr werden sollten, sind noch entsprechende Regeln hinzuzufügen. Möglicherweise wird man zukünftig auch CSS-Counter in Berechnungen verwenden können, sodass man sowas nicht mehr braucht.)

        Und da wird nicht mit festen Pixelgrößen rumhantiert. Wenn der Viewport kleiner wird, wird auch der Kreis kleiner.

        Sollen jetzt Buttons hinzukommen oder wegfallen, müssen die lediglich im DOM eingefügt/entfernt werden, und deren Anzahl muss in --count geändert werden. Deshalb steht das auch mit im Markup. Es ist kein Inline-Style, sondern das style-Attribut wird verwendet, um ein Datum aus dem Markup an CSS weiterzureichen.

        Und das ist auch nur Firefox geschuldet, der momentan :has() noch nicht richtig unterstützt. In Chromium und Safari können sich die Buttons schon selbst durchzählen. ☞ button circle, self-counting (wie oben, bis zu einem Dutzend)

        Kein --count mehr im Markup (und bei dynamischen Änderungen im JavaScript). Einfach nur Buttons im DOM einfügen/entfernen. Die Plazierung übernimmt das Stylesheet.

        🖖 Живіть довго і процвітайте

        PS: Das Beispiel verwendet aktuelles CSS mit :has(), mathematischen Funktionen, Container-Einheiten, Nesting – all das, wovon sogenannte „Frontend-Entwickler“ noch nie gehört haben.

        --
        Ad astra per aspera
        1. Hallo Lars,

          wenn Du wissen willst, was zum grundgütigen Geier dieses & im Stylesheet bedeutet, schau in den Selfhtml-Blog ("Schicke Schachteln")…

          Rolf

          --
          sumpsi - posui - obstruxi
        2. Hallo Gunnar,

          ich habe natürlich zuerst einen HTML / CSS Kurs gemacht. Aber in so einem Kurs werdem einem nur Grundlagen beigebracht. Bei Deinem Beispiel würde ich sagen, verstehe ich 75 - 80% vom CSS. Den Rest muss ich mir mal genauer ansehen.

          Gruß Lars

          1. @@webity

            Bei Deinem Beispiel würde ich sagen, verstehe ich 75 - 80% vom CSS. Den Rest muss ich mir mal genauer ansehen.

            Ich mir auch. 😏 Mir kam die Idee, da einen Vortrag draus zu machen – über die restlichen 20–25%.

            🖖 Живіть довго і процвітайте

            --
            Ad astra per aspera
            1. @@Gunnar Bittersmann

              Ich mir auch. 😏 Mir kam die Idee, da einen Vortrag draus zu machen – über die restlichen 20–25%.

              Oder zwei Vorträge. Den ersten Teil gab’s schon, die Fortsetzung noch nicht.

              🖖 Живіть довго і процвітайте

              --
              Ad astra per aspera
              1. @@Gunnar Bittersmann

                Ich mir auch. 😏 Mir kam die Idee, da einen Vortrag draus zu machen – über die restlichen 20–25%.

                Oder zwei Vorträge. Den ersten Teil gab’s schon, die Fortsetzung noch nicht.

                Das wird dieses Jahr auch nichts mehr mit der Fortsetzung. Ich hab jetzt die komplette Präsentation schon mal online gestellt, incl. Links zu Codepens und weiteren Ressourcen.

                🖖 Живіть довго і процвітайте

                --
                Ad astra per aspera
        3. @@Gunnar Bittersmann

          Und das ist auch nur Firefox geschuldet, der momentan :has() noch nicht richtig unterstützt. In Chromium und Safari können sich die Buttons schon selbst durchzählen. ☞ button circle, self-counting (wie oben, bis zu einem Dutzend)

          Ich hab in diesem Codepen einen Polyfill ergänzt. Nur, wenn der Browser diesen nötig hat (was mit !CSS.supports('selector(&:has(button:nth-of-type(1):last-of-type))') abgefragt wird), wird für alle Elemente der Klasse button-circle (es könnte ja mehrere davon auf der Seite geben) --count auf die Anzahl der Buttons gesetzt – initial und über einen MutationObserver bei späteren Änderungen im DOM.

          🖖 Живіть довго і процвітайте

          --
          Ad astra per aspera
        4. @@Gunnar Bittersmann

          Und das ist auch nur Firefox geschuldet, der momentan :has() noch nicht richtig unterstützt. In Chromium und Safari können sich die Buttons schon selbst durchzählen.

          Firefox kann das auch schon; ist aber noch hinter einem Flag versteckt.

          Wenn man layout.css.has-selector.enabled auf true setzt, geht das auch schon ohne Polyfill:

          ☞ button circle, self-counting (wie oben, bis zu einem Dutzend)

          🖖 Живіть довго і процвітайте

          --
          Ad astra per aspera
          1. Hallo Gunnar,

            PS: Das Beispiel verwendet aktuelles CSS mit :has(), mathematischen Funktionen, Container-Einheiten, Nesting – all das, wovon sogenannte „Frontend-Entwickler“ noch nie gehört haben.

            Ich bin nicht mal ein „Frontend-Entwickler“. Ich bin Anfänger und habe geglaubt etwas zu wissen. 😀

            Bei diesem Beispiel:

            https://codepen.io/gunnarbittersmann/pen/mdazbJQ

            & mit diesem Zeichen wird die Baumstruktur abgebildet - Ein Element enthält ein anderes.

            Mit der Zeile:

            &:has(button:nth-of-type(1):last-of-type) { --count: 1 }

            wird ermittelt, wie viele button vorrhanden sind und die Variable count dementsprechend gesetzt.

            dann stehen da Angaben zu dem button (Aussehen etc.) dann werden der Winkel, der Radius und die x, y Verschiebung berechnet.

            1. Frage: die Anzahl der Button soll sich ja ändern, muss ich also den
              Selektor :has() sooft einsetzen / schreiben, wie die maximale Anzahl an Button habe?

            2. Was mir noch nicht klar ist, es wird ein Button konfiguriert, es werden Berechnungen angestellt und am Ende werden den Buttons den Index zugeteilt. Wie greift da die Berechnung? AH - jetzt: zu jedem Button dem ein Index zugewiesen wird , gehören auch die Werte, Berechnungen etc. und so kann alles berechnet werden. Richtig?

            3. Wie funktioniert das mit dem Translate? Wo ist der Anfangspunkt?

              Ich habe es vom Div Mitte = Kreismittelpunkt in px berechnet. Aber in Deinem Beispiel ist mir das ein bisschen unklar.

            Ich weiß noch nicht welche Version ich verwende, Rolf seine Tipps setzte ich gerade um und räume ein bisschen auf. Beides interessiert mich.

            Gruß

            Lars

            1. Hallo Lars,

              zum &-Zeichen
              Gunnar verwendet das relativ neue Konzept des CSS Nesting. Schau in meinen Self-Blog Beitrag, den ich daraufhin geschrieben habe und der auf den zugehörigen Self-Wiki Eintrag verweist, da wird das erklärt (hoffentlich verstehbar 😉).

              zur Frage 1
              Ja. Maximal N Buttons ⇒ genau 2×N :has()-Regeln (N Stück für --count und nochmal N Stück für --index)

              zur Frage 2
              Genauuuu

              zur Frage 3
              Der circle Container verwendet Grid-Layout. Und zwar ein Grid mit einer Zeile und einer Spalte, also einer einzigen Zelle, die genauso groß wie der Container. Klingt doof? Ja, aber ist es nicht, denn Du bekommst damit zwei Dinge geschenkt:

              • Alle Buttons können in dieser Zelle platziert werden und liegen damit übereinander
              • Du kannst die Buttons bequem in der Zelle zentrieren, mit place-self: center.

              Die Alternative wäre absolute Positionierung und ein Herumrechnen mit der Position, oder ein Fummeln mit dem Padding. So ist's viel klarer, was passiert. Solange man das Grid-Layoutmodell kennt…

              Ausgehend von dieser Zentrierung werden die Kreise nun um den berechneten Radiuswert nach außen geschoben. Hier kommt ein weiterer Trick ins Spiel: 50cqw. Das ist eine Einheit aus dem CSS Containermodell und bedeutet: 50% der Breite meines Containers. Die CSS Eigenschaft container-type: inline-size macht aus dem .button-circle-Element einen Größencontainer, deswegen ist die Einheit anwendbar. Und warum nicht einfach var(--size) / 2? Der Container ist responsiv, er ist 20em groß, wenn Platz ist, andernfalls verkleinert er sich so, dass er maximal so breit und hoch ist wie der Bildschirm. Das erreicht Gunnar durch eine Begrenzung der Containerbreite auf das Minimum von var(--size), 100% (Breite des Body) und 100vh - 16px. 100vh ist 100% Viewporthöhe, und 16px sind die 8px Margin, die der Body browserseitig oben und unten bekommt. Pfui, Gunnar, eine magische Zahl!1!!elf! Jedenfalls bedeutet die Responsivität, dass var(--size) nicht unbedingt die wahre Größe des Containers enthält. 50cqw hingegen schon.

              Die Richtung, um die verschoben wird, berechnet sich dann standardmäßig mit Sinus und Cosinus. In der Mathematik haben wir zwar gelernt, dass der Cosinus für die X-Richtung und der Sinus für die Y-Richtung zuständig ist, aber dort zeigt der 0°-Winkel auch nach rechts, der Winkelwert steigt gegen den Uhrzeigersinn und die y-Koordinate zeigt nach oben. Im Browser zeigt die y-Koordinate nach unten und Gunnars Modell lässt den 0°-Winkel nach oben zeigen und den Winkelwert im Uhrzeigersinn ansteigen, deswegen die abweichende Verwendung von sin und cos und der Faktor -1 für die y-Koordinate.

              Tatsächlich steckt in diesen unschuldigen Zeilen also eine Menge CSS-Knowhow drin, das einem Einsteiger nicht unbedingt offensichtlich ist, deshalb habe ich mir jetzt die Mühe dieser langen Antwort gemacht.

              Es ist zwar nett, eine CSS-only Lösung zu haben. Aber ich hätte mir vermutlich doch die Mühe gemacht, mit JavaScript die Werte von --count und --index zu bestimmen. An dieser Stelle fehlen CSS noch Features, aber die Diskussion läuft schon und wenn Tab Atkins und Jake Archibald da die Finger dran haben, könnte auch was passieren.

              Rolf

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

                vielen Dank für diese sehr ausführliche Erklärung. Das muss ich ich erstmal ein paar mal lesen, um das zu verinnerlichen. Inzwischen werde ich meine Javascript Version weiter verbessern.

                Noch mal vielen Dank und einen schönen Rest - Sonntag.

                Gruss

                Lars

              2. @@Rolf B

                Danke, besser hätte ich’s auch nicht erklären können. Kann ich mir das sparen und relaxen. Vitaly (Mr. Smashing Magazine) hat uns heute durch die Stadt gescheucht.

                zur Frage 1
                Ja. Maximal N Buttons ⇒ genau 2×N :has()-Regeln (N Stück für --count und nochmal N Stück für --index)

                Es dürfen auch ein paar mehr sein, für den Fall der Fälle, wenn man die maximale Anzahl in Vorraus nicht genau kennt.

                Ein CSS-Präprozesser kann einem da Arbeit abnehmen.

                zur Frage 3

                • Du kannst die Buttons bequem in der Zelle zentrieren, mit place-self: center.

                Da muss ich nochmal ran. Wenn man dem Container outline verpasst, sieht man, dass der Mittelpunkt des Kreises nicht genau mit dem Mittelpunkt der Box zusammenfällt. Meh.

                16px sind die 8px Margin, die der Body browserseitig oben und unten bekommt. Pfui, Gunnar, eine magische Zahl!1!!elf!

                Ja, besser ist wohl, body {margin: 0} zu setzen, wie ich’s im anderen Beispiel gemacht habe.

                🖖 Живіть довго і процвітайте

                --
                Ad astra per aspera
                1. @@Gunnar Bittersmann

                  Da muss ich nochmal ran. Wenn man dem Container outline verpasst, sieht man, dass der Mittelpunkt des Kreises nicht genau mit dem Mittelpunkt der Box zusammenfällt. Meh.

                  Aber das kriegt man hin: CSS dial, Arabic numerals, upright (translate).

                  Zum Ergebnis kommt man auch mit Hin- und Herdrehen: CSS dial, Arabic numerals, upright (rotate).

                  🖖 Живіть довго і процвітайте

                  --
                  Ad astra per aspera
              3. @@Rolf B

                Es ist zwar nett, eine CSS-only Lösung zu haben. Aber ich hätte mir vermutlich doch die Mühe gemacht, mit JavaScript die Werte von --count und --index zu bestimmen.

                Hab ich auch gemacht – als Polyfill für Browser, die :has() noch nicht unterstützen (sprich: für Firefox).

                Mit if (!CSS.supports('selector(&:has(li:nth-of-type(1)))')) wird abgefragt, ob der jeweilige Browser das CSS versteht oder ob er das JavaScript ausführen muss.

                🖖 Живіть довго і процвітайте

                --
                Ad astra per aspera
          2. @@Gunnar Bittersmann

            Firefox kann das auch schon; ist aber noch hinter einem Flag versteckt.

            Jetzt nicht mehr.

            Wenn man layout.css.has-selector.enabled auf true setzt

            Was jetzt schon ohne Zutun der Fall ist. Firefox unterstützt :has(). \o/

            🖖 Живіть довго і процвітайте

            --
            Ad astra per aspera