envoy: Bildauswahl Reset

Hallo, ich habe 2 separate Bildauswahl-Elemente. Ich möchte beide, wenn ich auf den Button Reset drücke, jeweils auf das 1. Bild zurücksetzten. Einzeln kann ich ansprechen, weiß aber nicht, wie ich beide gleichzeitig zurücksetzten kann?

<button class="tablinks" onclick="changeImage('01.jpg', '#bannerImage_(**all???**)')">reset</button>

Viele Grüße

 </head>
    <body>
        <div class="tab">
            <button class="tablinks" onclick="openCity(event, 'left')">left</button>
            <button class="tablinks" onclick="openCity(event, 'right')">right</button>
            
        </div>
        <div id="left" class="tabcontent">
            <img id="bannerImage_left" src="01.jpg">
            <img src="01_thumb.jpg" onclick="changeImage('01.jpg', '#bannerImage_left')" style="cursor:pointer; margin-left:40px; margin-right:40px">
            <img src="02_thumb.jpg" onclick="changeImage('02.jpg', '#bannerImage_left')" style="cursor:pointer; margin-right:40px">
            <img src="03_thumb.jpg" onclick="changeImage('03.jpg', '#bannerImage_left')" style="cursor:pointer; margin-right:40px">
        </div>
        <div id="right" class="tabcontent">
            <img id="bannerImage_right" src="01.jpg" "">
            <img src="01_thumb.jpg" onclick="changeImage('01.jpg', '#bannerImage_right')" style="cursor:pointer; margin-left:40px; margin-right:40px">
            <img src="02_thumb.jpg" onclick="changeImage('02.jpg', '#bannerImage_right')" style="cursor:pointer; margin-right:40px">
            <img src="03_thumb.jpg" onclick="changeImage('03.jpg', '#bannerImage_right')" style="cursor:pointer; margin-right:40px">
        </div>

        <div>
        <button class="tablinks" onclick="changeImage('01.jpg', '#bannerImage_(**all???**)')">reset</button>
        </div>
        <script>
  1. Hallo envoy,

    Du könntest ganz stumpf im Reset-Button dies tun:

    <button onclick="changeImage('01.jpg', '#bannerImage_left'); changeImage('01.jpg', '#bannerImage_right')">RESET</button>
    

    Aber...

    Stop zum ersten. <img> ist kein interaktives Element. Da muss ein Button drumrum und für den kannst Du auf click reagieren.

    Stop zum zweiten: <img> braucht zwingend ein alt-Attribut. Bei dekorativen Bildern kann es leer sein, bei Bildern, die Inhalt darstellen, muss alt einen beschreibenden Text für das Bild enthalten.

    Stop zum dritten: Deine Thumbnails sind eine Liste, gelle? Also solltest Du sie auch als solche auszeichnen. Im CSS kannst Du mit list-style:none die Listenpunkte entfernen, mit padding das Padding des ul beseitigen und mit display:flex dafür sorgen, dass die Thumbs nebeneinander liegen. Mit overflow-x:auto könntest Du sie ggf. auch horizontal scrollbar machen, und wenn Du dann noch gap:1em hinzufügst (passend zum horizontalen Padding des ul), hast Du auch Abstand zwischen den Thumbs.

    Der Name des Vollbilds kann in einem data-Attribut hinterlegt werden. Das fragt sich im JavaScript über die dataset-Eigenschaft ab.

    Die Links/Rechts div könnten auch eine section sein. Die Klasse tabcontent ist suboptimal, finde ich, das ist eine Galerie und warum sollte sie dann nicht "gallery" heißen? Dem großen Bild kann man auch eine Klasse "banner" geben, um es mit JavaScript besser zu finden.

    Möchtest Du vielleicht einen Text zum Bild anzeigen? In dem Fall sollte dein Hauptbild in eine figure mit figcaption eingehüllt werden.

    <section id="left" class="gallery">
      <img class="banner" src="..." alt="...">
      <ul>
        <li><button type="button">
          <img src="pic01_thumb.png" data-pic="pic01.png" alt="...">
        </button></li>
        <li><button type="button">
          <img src="pic02_thumb.png" data-pic="pic02.png" alt="...">
        </button></li>
        <li><button type="button">
          <img src="pic03_thumb.png" data-pic="pic03.png" alt="...">
        </button></li>
        <li><button type="button">
          <img src="pic04_thumb.png" data-pic="pic04.png" alt="...">
        </button></li>
      </ul>
    </section>
    

    Stylesheet (CSS Datei oder <style>-Element):

    .gallery ul {
       display: flex;
       list-style: none;
       padding: 0 1em;
       gap: 1em;
       overflow-x: scroll;
    }
    .gallery li button {
       padding: 0;
       border: none;
       cursor: pointer;
    }
    

    Den Pointer setzt Du im Stylesheet, und die Margins bei den Thumbs brauchst Du nicht mehr, darum kümmert sich das Padding der Liste und die gap-Eigenschaft.

    Die Frage nach der click-Behandlung kann man auf 2 Arten beantworten. Du kannst natürlich weiterhin die Buttons mit onclick zuschmeißen. Würde ich aber nicht tun. Dafür gibt's unobstrusive Javascript. Dieses Script sollte am Ende des body stehen ODER im script-Element sollte das defer-Attribut stehen, damit Du nicht mit einem DOMContentLoaded Handler rumfummeln musst.

    Ich registriere einen click-Handler auf dem body. Egal, welchen Button du klickst, das Event blubbert im DOM nach oben und gelangt hinein. Deshalb wird im Handler als erstes geprüft, ob das überhaupt ein Klick auf ein Thumbnail-Image war.

    event.target enthält das Element, auf das geklickt wurde. Vermutlich das thumb-Image.
    event.target.closest("button") geht die Elternkette hoch und stoppt beim ersten Button.
    event.target.closest(".gallery") geht die Elternkette hoch und stoppt beim ersten Element mit Klasse gallery (dafür sorgt der Punkt vor gallery!).

    Auf diese Weise orientiert sich der click-Handler automatisch im DOM und muss gar nicht gesagt bekommen, für welche Hälfte er agieren soll.

    Wenn kein button oder keine Galerie gefunden wird, bricht der Handler ab. Andernfalls sucht er sich das Thumb-Image im Button und das Banner-Image in der Galerie (deswegen die banner-Klasse auf dem Banner-Bild!) und überträgt src und alt (deine changeImage-Funktion wird ähnliches tun, oder?). Die Suche nach dem thumb-Image im Button ist vielleicht übervorsichtig, aber je nach Seitengestaltung kann es auch sein, dass event.target doch nicht das Thumb-Bild ist, sondern Gedöns drumherum.

    document.body.addEventListener("click", function(event) {
       let button = event.target.closest("button");
       let galerie = event.target.closest(".gallery");
    
       // Ist es ein Button in einer Galerie? Wenn nicht, nichts tun.
       if (!button || !galerie) return;
    
       // Thumb-Image und Banner-Image Elemente finden
       let thumb = button.querySelector("img");
       let banner = galerie.querySelector(".banner");
    
       // Banner aus Thumb-Informationen befüllen
       banner.src = thumb.dataset.pic;
       banner.alt = thumb.alt;
    });
    
    // Der Reset-Button braucht eine ID, damit wir ihn eindeutig finden
    document.querySelector("#reset").addEventListener("click", function() {
       let galleries = document.querySelectorAll(".gallery");
       for (let gallery of galleries) {
          let banner = galerie.querySelector(".banner");
          let thumb1 = gallery.querySelector("button img");
    
          // Banner aus Thumb-Informationen befüllen
          banner.src = thumb.dataset.pic;
          banner.alt = thumb.alt;
       }
    });
    

    Für den Reset gibt's dann eine ziemlich generische Lösung. Das Script sucht sich alle Galerien und durchläuft sie (for...of-Schleife). Je Galerie sucht es sich das erste Image in einem Button und verwendet seine Werte für das Banner-Image.

    Fettich 😉 Ich hoffe, der Kram funktioniert auch, das ist 100% ungetestet.

    Rolf

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

      seit Jahren bin ich der Überzeugung, dass eine for-Schleife besser durch eine forEach-Funktion ersetzt werden sollte, weil der neue Scope in der Callback-Funktion die Möglichkeit bietet, mit Closures zu arbeiten. Das mag für Anfänger zwar zu viel auf einmal sein, weil Closures kein Anfänger-Thema sind, aber anstelle einer Schleife forEach zu benutzen, sollte ohne großen Aufwand zu verstehen sein.

         let galleries = document.querySelectorAll(".gallery");
         for (let gallery of galleries) {
            let banner = galerie.querySelector(".banner");
            let thumb1 = gallery.querySelector("button img");
      
            // Banner aus Thumb-Informationen befüllen
            banner.src = thumb.dataset.pic;
            banner.alt = thumb.alt;
         }
      });
      

      Warum nicht so?

      document.querySelectorAll(".gallery").forEach(gallery => {
        const
          banner = gallery.querySelector(".banner"),
          thumb = gallery.querySelector("button img");
      
        // Banner aus Thumb-Informationen befüllen
        if (banner && thumb) {
          banner.src = thumb.dataset.pic;
          banner.alt = thumb.alt;
        }
      });
      

      Liebe Grüße

      Felix Riesterer

      1. Warnung an diejenigen, die keine Programmierstil-Enthusiasten sind: Haarspalterei voraus!

        Hallo Felix,

        ich habe großen Respekt vor deiner Kompetenz und Erfahrung, aber ich glaube trotzdem, dass Dir hier ein Irrtum rausgerutscht ist und Du Closure mit Scope verwechselst.

        Allgemein entsteht eine Closure, wenn ein Funktionsobjekt erzeugt wird und der Code der Funktion auf Variablen zugreift, die außerhalb der Funktion deklariert wurden. Die Closure ist das Bündel, das diese Variablen „rettet“, falls das Funktionsobjekt seinen erzeugenden Scope überlebt. Wie das technisch abläuft, ist ein Implementierungsdetail und hier unwichtig. Aber eins ist wichtig: Die Parameter und lokalen Variablen der Funktion sind nicht Teil der Closure. Können sie auch nicht sein, denn die Variablen, die in der Closure enthalten sind, leben so lange weiter wie das Funktionsobjekt besteht. Die lokalen Variablen hingegen leben gerade so lange, wie ein Aufruf dieser Funktion dauert (es sei denn, natürlich, dass dieser Funktionsaufruf eine weitere Closure erzeugt…)

        Im konkreten Fall besteht überhaupt kein Bedarf für eine Closure. Der Schleifenrumpf greift nur auf gallery zu, der bei mir die foreach-Laufvariable ist und bei Dir der Callback-Parameter. banner und thumb1 sind lokal, und hier stimme ich Dir bei: const ist richtig, let war falsch.

        Durch die Funktion sind banner und thumb1 im lokalen Scope der Callback-Funktion eingeschlossen. Das ist gut. Wenn ich mit var gearbeitet hätte, dann wären banner und thumb1 bei mir nicht lokal gewesen, sondern im globalen Scope. Das wäre schlecht.

        Aber: das habe ich nicht. Ich habe let verwendet, und let hat – wie const auch – nicht Funktionsscope, sondern Blockscope. Damit sind auch bei mir banner und thumb1 lokal im Schleifenrumpf.

        Meine Variable galleries (die auch lieber ein const geworden wäre) ist übrigens nur der Einsteiger-Lesbarkeit wegen drin, ich hätte den zugewiesenen Wert auch direkt hinter dem of notieren können.

        Was ist nun der Unterschied zwischen .forEach und for...of?

        • .forEach muss pro Durchlauf eine Callbackfunktion aufrufen. Für for...of muss hingegen ein Iteratorobjekt erzeugt und dieser Iterator durchlaufen werden. Der Performanceunterschied dürfte sich im Nanosekundenbereich bewegen, ich habe es nicht gemessen.
        • Wie schon diskutiert: der .forEach-Callback ist eine Funktion und kapselt damit auch var-Variablen. for...of tut das nicht. Vermeidet man var, wird das irrelevant.
        • .forEach benötigt voneinander entfernte Klammerpaare, die balanciert bleiben müssen. Die for...of-Schleife hat dagegen die runden Klammern beieinander und nur die geschweiften Klammern sind voneinander entfernt.
        • .forEach liefert außer dem Schleifenwert auch den aktuellen Schleifenindex und das iterierte Objekt als weitere Parameter. Wenn man das braucht, hat .forEach Vorteile. Braucht man es nicht, ist es unnötiger Overhead.
        • Dadurch, dass .forEach den Schleifenrumpf als Funktion erwartet, kann man den Rumpf einmal programmieren und für mehrere Schleifen wiederverwenden. Aus meiner Sicht wenig nützlich und ggf. sogar verwirrend. Rumpf und Schleife gehören normalerweise zusammen. Heißt: wenn ich den Schleifenrumpf wiederverwenden will, kann ich auch das for...of mit in die Funktion schieben und die zu durchlaufende Kollektion als Argument übergeben.
        • .forEach ist funktional und for...of ist prozedural. Ob das vor- oder nachteilig ist, ist eine Sache des persönlichen Geschmacks und des Stils, in dem die gesamte Anwendung erstellt wird.

        Deswegen finde ich for...of für iterierbare Kollektionen heute die bessere Lösung. Die .forEach-Methode hat Nutzen, wenn eine Kollektion keinen Iterator hat, oder wenn ich die zusätzlichen Parameter des .forEach-Callbacks brauche.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Danke für die ausführliche Antwort, probiere ich bei mir aus.

        2. Lieber Rolf,

          aber ich glaube trotzdem, dass Dir hier ein Irrtum rausgerutscht ist und Du Closure mit Scope verwechselst.

          das kann grundsätzlich immer passieren, aber hier ging es mir nur um die Möglichkeit, Closures nutzen zu können. Dass im vorliegenden Fall keine Closure vorkommt, ist mir natürlich klar gewesen. Es gibt aber Fälle, bei denen man innerhalb einer Schleife gewisse Dinge nicht so einfach tun kann, weil es in einer Schleife selbst keine Closures gibt, was am prozeduralen Kontext liegt. Ein besseres Szenario bietet mein Tutorial zum Umgang mit Callback-Funktionen, darin das Kapitel Callback-Funktionen als Kapselungen.

          • .forEach muss pro Durchlauf eine Callbackfunktion aufrufen. Für for...of muss hingegen ein Iteratorobjekt erzeugt und dieser Iterator durchlaufen werden. Der Performanceunterschied dürfte sich im Nanosekundenbereich bewegen, ich habe es nicht gemessen.

          Irgendwo habe ich mal gelesen, dass Funktionsaufrufe gut parallelisierbar wären, weshalb der funktionale Stil auf Mehrkernsystemen bessere Leistung brächte. Aber das ist mir völlig egal, mir geht es um den Schreibstil, der für mich persönlich überschaubarer ist.

          • .forEach benötigt voneinander entfernte Klammerpaare, die balanciert bleiben müssen. Die for...of-Schleife hat dagegen die runden Klammern beieinander und nur die geschweiften Klammern sind voneinander entfernt.

          Syntax muss stimmen. Immer. Das sehe ich weder als Vor-, noch als Nachteil. Die Klammern um die Geschichte herum müssen halt passen. Aber die Variablen in der Funktion funktionieren nun einmal etwas anders, als in einer Schleife.

          • .forEach liefert außer dem Schleifenwert auch den aktuellen Schleifenindex und das iterierte Objekt als weitere Parameter. Wenn man das braucht, hat .forEach Vorteile. Braucht man es nicht, ist es unnötiger Overhead.

          Den merke ich aber nicht, wenn ich in der Callback-Funktion nur ein Argument entgegen nehme, denn der Index ist ein zweites Argument, das ich auch ignorieren kann. Stört also nicht.

          • Dadurch, dass .forEach den Schleifenrumpf als Funktion erwartet, kann man den Rumpf einmal programmieren und für mehrere Schleifen wiederverwenden. Aus meiner Sicht wenig nützlich und ggf. sogar verwirrend. Rumpf und Schleife gehören normalerweise zusammen. Heißt: wenn ich den Schleifenrumpf wiederverwenden will, kann ich auch das for...of mit in die Funktion schieben und die zu durchlaufende Kollektion als Argument übergeben.

          In der Praxis verwende ich anonyme Callback-Funktionen, weil die Verarbeitung der Elemente einer Liste fast immer individuell ist, sodass ich auch eine individuell gestaltete Callback-Funktion benötige. Nur selten definiere ich diese eigenständig, um sie dann über ihren Namen im forEach-Aufruf zu referenzieren.

          • .forEach ist funktional und for...of ist prozedural. Ob das vor- oder nachteilig ist, ist eine Sache des persönlichen Geschmacks und des Stils, in dem die gesamte Anwendung erstellt wird.

          Und genau hier tendiert mein persönlicher Geschmack sehr stark zur funktionalen Schreibweise. Ich mache mir einfach keine Gedanken mehr darüber, welche Art der for-Schleife ich benötige:

          for (let i = 0; i < myArray.length; i++) { myArray[i] = false; }
          myArray.forEach(element => element = false);
          
          for (myProperty in myObject) { myObject[myProperty] = false; }
          Object.keys(myObject).forEach(myProperty => myObject[myProperty] = false);
          
          for (myValue of myObject) { console.log(myValue); }
          Object.values(myObject).forEach(myValue => console.log(myValue));
          

          Zugegeben, im letzten Fall würde ich mir einen Wolf schreiben. Aber für mich ist es gedanklich die immer gleiche Art, über eine Liste zu iterieren, egal ob es jetzt Elemente in einem Array sind, Elemente in einer NodeList, oder Eigenschaften eines Objekts. Im Zweifel erzeuge ich mir mit Array.from() ein Array, um forEach zur Verfügung zu haben:

          Array.from(node.getElementsByTagName("p")).forEach(p => {
            // tu was mit p
          });
          

          Deswegen finde ich for...of für iterierbare Kollektionen heute die bessere Lösung. Die .forEach-Methode hat Nutzen, wenn eine Kollektion keinen Iterator hat, oder wenn ich die zusätzlichen Parameter des .forEach-Callbacks brauche.

          Für mich hat sie den Nutzen, dass ich auf die immer gleiche Weise vorgehen kann.

          Liebe Grüße

          Felix Riesterer

          1. Hallo Felix,

            denn der Index ist ein zweites Argument, (...das nicht stört)

            Habe ich auch so nicht gemeint. Ich habe es nur als einen potenziellen Vorteil für .forEach angeführt. Und einen potenziellen Performancenachteil im Nanosekundenbereich.

            Es gibt aber Fälle, bei denen man innerhalb einer Schleife gewisse Dinge nicht so einfach tun kann, weil es in einer Schleife selbst keine Closures gibt, was am prozeduralen Kontext liegt.

            Das ist falsch. Geschweifte Klammern bilden einen Blockscope für Variablen, die mit let oder const definiert wurden, und mit dem lässt sich eine Closure bilden.

            let works = [];
            for (let i=0; i<10; i++) {
               works.push(() => i);
            }
            for (let w of works)
               console.log(w());
            

            Gibt 1 bis 9 aus. Wenn man i dagegen vorher definiert, ist es Teil des äußeren Scopes und man sieht neunmal die 10. Anzumerken ist aber, dass es JS-Bugs gab, bei denen die let-Definition im for-Statement die Schleifenvariable nicht in den Blockscope hineinnahm (siehe bei Kangax ES6, Bindings / let / "for/for-in loop iteration scope").

            Ich hätte noch mehr zu sagen, habe jetzt aber keine Zeit mehr.

            Und dann tut mir jetzt ja beinah leid, aber ich muss Dir ein Gericht aus deinen eigenen Worten kredenzen. Bitte alles aufessen.

            Vorspeise:

            for (let i = 0; i < myArray.length; i++) { myArray[i] = false; }
            myArray.forEach(element => element = false);
            

            Autsch. Das ist definitiv nicht äquivalent.

            • element ist keine Referenz auf das originale Arrayelement. Wenn Du es überschreibst, bleibt das Array unberührt.
            • Wenn Du ein sparse array[1] hast, füllt die for(;;)-Schleife die Lücken auf. forEach tut das nicht. Hier muss man sich entscheiden, welches von den beiden Verhalten man wünscht.

            So würde es gehen, solange man nicht ein sparse array auffüllen muss:

            myArray.forEach((element, i, array) => array[i] = false);
            

            Für for...of bräuchte man einen mühseligen Workaround, der zu .forEach() äquivalent wäre:

            for (let index of Object.keys(myArray)) { myArray[index] = false; }
            

            Aber das würde ich nicht für empfehlenswert halten.

            Hauptgericht:

            for (myProperty in myObject) { myObject[myProperty] = false; }
            Object.keys(myObject).forEach(myProperty => myObject[myProperty] = false);
            

            Autsch, das ist auch nicht äquivalent. for...in durchläuft auch die Namen der geerbten Propertys. Object.keys() hingegen nicht.

            Nachspeise:

            for (myValue of myObject) { console.log(myValue); }
            

            listet nicht die Werte aller Objekteigenschaften, sondern bricht ab, wenn myObject ein Objekt ist, das keine Iterator-Schnittstelle anbietet. Da muss schon Object.values(myObject) hin, wenn Du über die Propertywerte iterieren willst (was aber ohne deren Namen nicht sinnvoll ist). Besser wäre für diesem Zweck entries:

            for (myEntry of Object.entries(myObject)) {
               console.log(`${myObject[0]} => ${myObject[1]}`);
            }
            

            Rolf

            --
            sumpsi - posui - obstruxi

            1. sparse array:
              let a = [1,2,3];
              a[9]=4;
              Die Indizies 3 bis 8 sind unbesetzt. ↩︎

        3. @@Rolf B

          Deswegen finde ich for...of für iterierbare Kollektionen heute die bessere Lösung. Die .forEach-Methode hat Nutzen, wenn eine Kollektion keinen Iterator hat, oder wenn ich die zusätzlichen Parameter des .forEach-Callbacks brauche.

          So habe ich es in diesem Script: fast überall for...of; nur an einer Stelle brauch ich den Schleifenindex, dort .forEach. (Im Einsatz auf: Discovery seasons 1–4, Discovery main vs. parallel und Strange New Worlds real vs. flat.)

          Einen Unterschied merke ich auf Arbeit: Bei uns beim Tagesspiegel meckert der Linter über for...of; wir sollen nur .forEach verwenden. Warum, weiß ich jetzt auch nicht.

          Kwakoni Yiquan

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

            Discovery seasons 1–4, Discovery main vs. parallel

            Falls jemand die opening titles[1] der 3. Staffel mit credits[2] hat, immer her damit.

            Kwakoni Yiquan

            --
            Ad astra per aspera

            1. Wie sagt man auf deutsch? Vorspann? Auch wenn’s gar nicht am Anfang, sondern erst nach etlichen Minuten cold open kommt? ↩︎

            2. ?? ↩︎