bigRust: Frage zum Wiki-Artikel „JavaScript“

Ich habe einen Blog erstellt, bei dem rechts neben dem Beitragstext in einem Image-Slider verschiedene Bilder passend zum Beitrag durchlaufen sollen. Hierfür verwende ich folgendes Script:

var current = 0,
    slides = document.getElementsByClassName("slide");

setInterval(function() {
  for (var i = 0; i < slides.length; i++) {
    slides[i].style.opacity = 0;
  }
  current = (current != slides.length - 1) ? current + 1 : 0;
  slides[current].style.opacity = 1;
}, 3000);

Die HTML-Struktur sieht (vereinfacht) wie folgt aus:

<div class="blogbeitrag">			

     <p>Text</p>

     <div class="slider">
					<img class="slide" src="images/Bild1.jpg" alt="">
					<img class="slide" src="images/Bild2.jpg" alt="">
					<img class="slide" src="images/Bild3.jpg" alt="">
					<img class="slide" src="images/Bild4.jpg" alt="">
			</div>
</div>



<div class="blogbeitrag">			

     <p>Text</p>

     <div class="slider">
					<img class="slide" src="images/Bild5.jpg" alt="">
					<img class="slide" src="images/Bild6.jpg" alt="">
					<img class="slide" src="images/Bild7.jpg" alt="">
					<img class="slide" src="images/Bild8.jpg" alt="">
			</div>
</div>

Nun ist es so, dass der Slider sich auf alle Slider-Elemente in dem Blog bezieht. D.h. sobald Bild4 im ersten Beitrag fertig ist, "sliden" die Elemente unten weiter – oben wird es dafür schwarz.

Weiß jemand, was ich an dem Script ändern muss, damit das nicht passiert?

Danke für die Hilfe!

  • bigRust
  1. Hallo bigRust,

    sehe ich das richtig, dass Du auf einer Seite N Blogbeiträge hast und jeder kann einen Slider enthalten?

    Dann musst Du den setInterval-Callback Slider für Slider aufrufen. Das musst Du in einer Funktion tun, damit für jede Slider-Gruppe eine Closure gebildet wird und eine eigene slides-Nodelist sowie eine eigene current-Variable verfügbar ist. Wenn die global sind, so wie bei Dir, stören die Slider sich gegenseitig. Man kann das auch anders lösen, mit einer current-Klasse und DOM Methoden, aber eine current-Variable ist da schon am einfachsten.

    document.querySelectorAll(".slider").forEach(function(slider) {
       let slides = slider.querySelectorAll(".slide");
       let current = 0;
       setInterval(function() {
          for (var i = 0; i < slides.length; i++) {
             slides[i].style.opacity = 0;
          }
          current = (current != slides.length - 1) ? current + 1 : 0;
          slides[current].style.opacity = 1;
       }, 3000);
    });
    

    Die .getElementsByClassName Methode hat den Nachteil, dass sie nur einzelne Elemente betrachtet und prüft, ob darin eine der angegebenen Klassen vorhanden ist. Mit querySelectorAll kann man beliebige CSS Selektoren verwenden, um Elemente zu finden.

    Der Aufruf von querySelectorAll (oder auch getElementsByClassName) auf einem Element statt auf dem Dokument sucht dann nur innerhalb dieses Elements statt im ganzen Dokument. Das nutze ich, um nur die Slides des aktuellen Sliders zu finden.


    Das Innenleben von setInterval kann man noch optimieren:

    document.querySelectorAll(".slider").forEach(function(slider) {
       let slides = slider.querySelectorAll(".slide");
       let current = 0;
       setInterval(function() {
          slides.forEach( function(slide, index) {
             slide.style.opacity = (index == current ? 1 : 0);
          });
          current = (current + 1) % slides.length;
       }, 3000);
    });
    

    Hier wird die forEach-Methode verwendet, die ein NodeList Objekt schon länger kennt (seit dem Tod des Internet Explorers ist das unproblematisch). Diese ruft pro NodeList-Eintrag eine Funktion auf und übergibt ihr:

    • den Eintrag
    • seinen Index
    • die Nodelist

    Die Nodelist (den 3. Parameter) brauchen wir nicht, aber der 2. Eintrag ersetzt das i deiner for-Schleife.

    Mit Hilfe des ?: Operators kann dann auch gleich der opacity-Wert richtig gesetzt werden. Es ist ist nicht nötig, erstmal alle auf 0 zu setzen und dann den richtigen wieder auf 1.

    Um zu erreichen, dass ein Zähler von 0 bis (X-1) umläuft, muss man nicht abfragen, ob er (X-1) erreicht hat. Es ist viel einfacher, den Rest der Division durch X zu bestimmen (der % Operator).


    Auf die Variablen slides und current kann man auch ganz verzichten, wenn man statt setInterval mit setTimeout arbeitet. Denn bei setTimeout kann man ab dem dritten Parameter Werte angeben, die die timeout-Funktion erhalten soll, und damit kann man von Aufruf zu Aufruf einen angepassten Current-Wert weiterreichen:

    for (let slider of document.querySelectorAll(".slider"))
       setTimeout(runSlider, 3000, slider.querySelectorAll(".slide"), 0);
    }
    
    function runSlider(slides, current) {
       slides.forEach(
          (slide, index) => slide.style.opacity = (index == current ? 1 : 0)
       );
       setTimeout(runSlider, 3000, slides, (current+1)%slides.length);
    }
    

    Die for...of-Schleife – die verwendet werde kann, weil NodeList-Objekte netterweise iterierbar sind – startet einen ersten setTimeout für jeden Slider auf der Seite und übergibt der Timeout-Funktion die Liste der Slides und 0 als ersten current-Wert.

    Die Timeout-Funktion runSlider nimmt sich die slides und den current-Wert, zeigt diesen an und versteckt die übrigen. Danach startet sie einen neuen setTimeout mit sich selbst als Timeout-Funktion und übergibt für diesen Aufruf die unveränderte slides-Liste sowie den neuen current-Wert.

    Für das Innenleben des slides.forEach habe ich diesmal eine Pfeilfunktion verwendet, das ist kompakter als eine normale Funktion.

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Hi, Rolf!

      Danke vielmals – das ist eine Antwort, mit der ich etwas anfangen kann und die mir weiterhilft.

      Danke auch für die ausführliche Erklärung, ich hab es direkt ausprobiert und es war die Lösung für mein Problem.

      • bigRust