Beispiel:JS-Komfort-Bildwechsler.html

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" media="screen" href="./Beispiel:SELFHTML-Beispiel-Grundlayout.css">
<title>Komfort-Bildwechsler</title>
<style>
   
.gallery {
  & a {
    text-decoration: none;
    display: inline-block;
    
    &:focus {
      outline: 2px solid black;
    }
    
    & img {
      width: 100px;
      display: block;
    }
  }
}
    
#gallery-fullview {
  width: 100vw; height: 100vh;
  padding: 2em 5vw 8vh 5vw;
  background-color: #888;
  box-sizing: border-box;
  border-radius: 10px;

  &::backdrop { 
    background: rgb(200 200 200 /.8) ;
  }

  & figure {
    border: thin solid #aaa;
    margin: 0;
    box-sizing: border-box;
    width: 100%;
    height: 100%;
    background: #ccc;
  }

  & img {
    display: block;
    margin: auto;
    width: 100%;
    height: 100%;
    object-fit: contain;
  }

  & figcaption {
    font-style: italic;
    text-align: center;
    background-color: white;
  }

  & button {
    position: absolute;
    width: 2rem;
    height: 2rem;
    cursor: pointer;	
    border: 0;
    background-color: transparent;
    --fill: none;
    --stroke: white;
    
    &:focus {
      outline: none;
    }
    
    &[value=close],
    &[value=shuffle],
    &[value=play] {
      top: 0;
      right: 0;
      --fill: firebrick;
      
      &:hover,
      &:focus {
        --fill: red;
      }
    }
    
    &[value=shuffle],
    &[value=play] {
      & svg {
        &:nth-of-type(1) {
          display: inline;
        }
        
        &:nth-of-type(2) {
          display: none;
        }
      }
    }
    
    &[value=shuffle] {
      right: 4rem;	
      
      .shuffle & svg {
        &:nth-of-type(1) {
          display: none;
        }
        
        &:nth-of-type(2) {
          display: inline;
        }
      }
    }
      
    &[value=play] {
      right: 8rem;	
      
      .play & svg {
        &:nth-of-type(1) {
          display: none;
        }
        
        &:nth-of-type(2) {
          display: inline;
        }
      }
    }

    &[value=prev],
    &[value=next] {
      top: calc(50% - 100px);
      width: 100px;
      height: 200px;
      padding: 0;
      --stroke: #fff6;

      &:hover,
      &:focus {
        --stroke: skyblue;
      }
    }
    
    &[value=prev] {
      left: 2vw;
    }
    
    &[value=next] {
      right: 2vw;
    }
  }
}
    
.visually-hidden {
	position: absolute !important; 
	clip: rect(1px, 1px, 1px, 1px) !important; 
	padding: 0 !important; 
	border: 0 !important; 
	height: 1px !important; 
	width: 1px !important; 
	overflow: hidden !important; 
	white-space: nowrap !important; 
}
 
</style>
<script>
'use strict';  
document.addEventListener('DOMContentLoaded', function () {

  // Zeit in ms zum betrachten der Bilder im Automatik-Modus
  const zeitZumBetrachten = 1000;

  // click-Handler auf alle Galerien legen. KÖNNTE man auch auf den Body legen und Bubbling nutzen, aber dann bekommt
  // man mit currentTarget die Galerie nicht geschenkt (und muss sie vom target mit closest(''.gallery') suchen)
  document.querySelectorAll(".gallery")
          .forEach( gallery => gallery.addEventListener("click", handleGalleryClick) );
  // EIGENEN click-Handler auf den Fullview. 
  document.getElementById("gallery-fullview")
          .addEventListener("click", handleFullviewClickAndKeydown);
  // EIGENEN keydown-Handler aufs Dokument. 
  document.addEventListener("keydown", handleFullviewClickAndKeydown);

  // Klick in einer Galerie ist simpel, das behandelt alles die showInFullview Funktion, 
  // die auch vom handleFullviewClickAndKeydown zum navigieren verwendet wird.
  function handleGalleryClick(event) {
    // event.target ist das img Element oder welches HTML auch immer sonst im Thumb steckt. Suche
    // das a Element als Bezugspunkt für die Fullview-Anzeige heraus
    const clickedLink = event.target.closest("a");

    // Nicht auf einen Link geklickt? Nichts tun.
    if (!clickedLink) return;
    event.preventDefault();

    // Delegiere den Rest...
    showInFullview(clickedLink);
  }

  function showInFullview(link) {
          // der Fullscreen-Viewer
    const fullview = document.getElementById("gallery-fullview"),
          // die Galerie zum geklickten Link
          gallery = link.closest(".gallery"),
          // Das Thumbnail-Bild darin, brauchen wir für ...
          thumb = link.querySelector("img"),
          // ... den Caption-Text, der aus dem alt-Attribut generiert wird.
          caption = thumb ? thumb.alt : "";

    // Den angeklickten Link zum aria-selected Element machen
    link.parentElement.querySelectorAll("a[aria-selected]").forEach(link => link.removeAttribute("aria-selected"));
    link.setAttribute("aria-selected", "true");

    // src-Attribut im Vollbild auf das verlinkte Bild setzen und Caption in figcaption eintragen
    fullview.querySelector("img").src = link.href;
    fullview.querySelector("figcaption").textContent = caption;

    // Galerie-ID speichern für click-Handler im Fullview und Fullview einblenden (falls noch nicht passiert)
    fullview.dataset.gallery = gallery.id;
    fullview.showModal();
  }

  function handleFullviewClickAndKeydown(event) {
    const fullview = document.getElementById("gallery-fullview")
    if(!fullview) return;
    const gallery = document.getElementById(fullview.dataset.gallery);
    if(!gallery) return;
    const currentThumb = gallery.querySelector("a[aria-selected]");
    if (!currentThumb) return;
    let nextThumb,action="",stopped;
    
    if(event.type == "keydown") { 
      action = event.key; // Welche Taste wurde gedrückt?
    }
    if(event.type == "click") {
      const clickedButton = event.target.closest("button");
      // Nicht auf einen Button geklickt? Nichts tun.
      if (!clickedButton) return;
      action = clickedButton.value; // Welcher Button wurde geklickt?
    }

    switch (action) {
      case 'close':
      case 'Escape':
      case 'x':
        stopAnimations(fullview);
        fullview.close();
        fullview.dataset.gallery = null;
        gallery.querySelector("a[aria-selected]").focus();
        break;
      case 'prev':
      case 'ArrowLeft':
        // Ausführliche Version
        nextThumb = navigateDOM(currentThumb,
                                elem => elem.previousElementSibling,
                                elem => elem.tagName == "A");
        if (!nextThumb)
          nextThumb = gallery.querySelector("a:last-of-type")

        showInFullview(nextThumb);
        break;
      case 'next':
      case 'ArrowRight':
        // Kompaktversion als Einzeiler
        showInFullview(navigateDOM(currentThumb, elem => elem.nextElementSibling, elem => elem.tagName == "A") || gallery.querySelector("a:first-of-type"));
        break;
      case 'play':
      case 'r':
        // Evtl. laufende Animationen benden
        stopped = stopAnimations(fullview);
        if (!stopped.playStopped)  {
          // Mit setInterval die Bilder nacheinander zeigen (next animieren)
          fullview.play = setInterval(function() {
           showInFullview(navigateDOM(gallery.querySelector("a[aria-selected]"), elem => elem.nextElementSibling, elem => elem.tagName == "A") || 
                        gallery.querySelector("a:first-of-type"));
          }, zeitZumBetrachten);
          fullview.classList.add("play");
        }
        break;
      case 'shuffle':
      case 's':
        // Evtl. laufende Animationen benden
        stopped = stopAnimations(fullview);
        if (!stopped.shuffleStopped) {
          // Per Zufall das nächste Bilde ermitteln und anzeigen
          const allThumbs = gallery.querySelectorAll("a");
          let randomNumber,lastNumber=-1;
          fullview.shuffle = setInterval(function() {
            do {
              randomNumber = Math.floor(Math.random()*allThumbs.length);
            } while(randomNumber == lastNumber)
            showInFullview(allThumbs[randomNumber]);
            lastNumber = randomNumber;
          }, zeitZumBetrachten); 
          fullview.classList.add("shuffle");
        }
        break;
      case 'p':
        // Evtl. laufende Animationen benden
        stopAnimations(fullview);
        break;
    }
 
    /* Helper: Navigiere schrittweise durch's DOM, bis eine Bedingung erfüllt ist
     * "current" - Ausgangspunkt (ein HTML Elemnt)
     * Was das "nächste" Thumbnail-Element ist, legt der proceed-Callback fest.
     * Die Funktion sucht ausdrücklich nach a-Elementen - wenn andere Elemente im
     * Container sind, werden sie übersprungen. Wird kein Link mehr gefunden, gibt
     * die Funktion null zurück - um den Rest kümmere sich bitte der Aufrufer.
     */
    function navigateDOM(current, proceed, checkFound) {
      if (!current)
        return null;
      while (current = proceed(current)) {
        if (checkFound(current))
          break;
      }
      return current;
    }
    
    /* Helper: Beende evtl. laufende Animationen und gebe zurück, 
     * welche Animation beendet wurde. */
    function stopAnimations(fullviewElement) {
      let playStopped = false,
          shuffleStopped = false;
      if(fullview.play) {
        clearInterval(fullviewElement.play);
        fullviewElement.play = null;
        fullviewElement.classList.remove("play");
        playStopped = true;
      }
      if(fullviewElement.shuffle) {
        clearInterval(fullviewElement.shuffle);
        fullviewElement.shuffle = null;
        fullviewElement.classList.remove("shuffle");
        shuffleStopped = true;
      }
      return { "playStopped": playStopped, "shuffleStopped": shuffleStopped };
    }

  }
    
});
</script>

</head>
<body>
<h1>Komfort-Bildwechsler</h1>

<section id="peru" class="gallery">
  <h2>Peru 2007</h2>
  <a href="https://wiki.selfhtml.org/images/2/28/Peru-1.jpg">
    <img src="https://wiki.selfhtml.org/images/2/24/Peru-1-sm.jpg" alt="Peru 2007: Cusco - Blick auf Ausangate">
  </a>
  <a href="https://wiki.selfhtml.org/images/4/42/Peru-2.jpg">
    <img src="https://wiki.selfhtml.org/images/e/ea/Peru-2-sm.jpg" alt="Peru 2007: Valle Sagrado">
  </a>
  <a href="https://wiki.selfhtml.org/images/a/ab/Peru-3.jpg">
    <img src="https://wiki.selfhtml.org/images/c/c5/Peru-3-sm.jpg" alt="Peru 2007: Machu Picchu">
  </a>
  <a href="https://wiki.selfhtml.org/images/8/80/Peru-4.jpg">
    <img src="https://wiki.selfhtml.org/images/b/b0/Peru-4-sm.jpg" alt="Peru 2007: Machu Picchu - Lamas in den Ruinen">
  </a>
  <a href="https://wiki.selfhtml.org/images/3/3f/Peru-5.jpg">
    <img src="https://wiki.selfhtml.org/images/7/71/Peru-5-sm.jpg" alt="Peru 2007: Uros-Inseln im Titicaca-See">
  </a>
  <a href="https://wiki.selfhtml.org/images/4/41/Peru-6.jpg">
    <img src="https://wiki.selfhtml.org/images/5/5b/Peru-6-sm.jpg" alt="Peru 2007: Ceviche - Meeresfrüchte mit Zitronensaft">
  </a>
</section>

<section id="gardasee" class="gallery">
  <h2>Gardasee 2016</h2>
  <a href="https://wiki.selfhtml.org/images/f/fb/Gardasee1.jpg">
    <img src="https://wiki.selfhtml.org/images/8/8f/Gardasee1-sm.jpg" alt="Gardasee 2016: Sonnenuntergang bei Bardolino ">
  </a>
  <a href="https://wiki.selfhtml.org/images/1/16/Gardasee2.jpg">
    <img src="https://wiki.selfhtml.org/images/6/67/Gardasee2-sm.jpg" alt="Gardasee 2016: Aperol Spritz - Entspannung für Lehrer ">
  </a>
  <a href="https://wiki.selfhtml.org/images/3/3b/Gardasee-3.jpg">
    <img src="https://wiki.selfhtml.org/images/4/40/Gardasee-3-sm.jpg" alt="Gardasee 2016: Gardaland bei Bardolino - Blick von oben">
  </a>
  <a href="https://wiki.selfhtml.org/images/e/ef/Gardasee-4.jpg">
    <img src="https://wiki.selfhtml.org/images/1/16/Gardasee-4-sm.jpg" alt="Gardasee 2016: Wasserrutsche im Gardaland ">
  </a>
  <a href="https://wiki.selfhtml.org/images/1/15/Gardasee-5.jpg">
    <img src="https://wiki.selfhtml.org/images/c/c3/Gardasee-5-sm.jpg" alt="Gardasee 2016: Sonnenuntergang bei Bardolino">
  </a>
  <a href="https://wiki.selfhtml.org/images/5/5a/Gardasee-6.jpg">
    <img src="https://wiki.selfhtml.org/images/d/d4/Gardasee-6-sm.jpg" alt="Gardasee 2016: nächtliches Handballspiel ">
  </a>
  <a href="https://wiki.selfhtml.org/images/2/2e/Gardasee-7.jpg">
    <img src="https://wiki.selfhtml.org/images/a/a4/Gardasee-7-sm.jpg" alt="Gardasee 2016: Die antike Arena in Verona - heute für Konzerte und Opern genutzt ">
  </a>
</section>

<p>Bei einem Klick auf die <em>Thumbnail</em>-Vorschau erhalten Sie eine Großansicht.</p>
 
<dialog id="gallery-fullview">
  <figure>
    <img alt="described by figcaption" src='data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg"%3E%3C/svg%3E'>
    <figcaption></figcaption>
  </figure>
  <button type="button" value="close"><span class="visually-hidden">Schließen</span>
    <svg viewBox='0 0 100 100'>
      <rect width='100' height='100' fill='var(--fill)' />
      <path d='M20,20 l60,60 m0,-60 l-60,60' fill='none' stroke='white' stroke-width='15' stroke-linecap='round'/>
     </svg>
   </button>
  <button type="button" value="play" aria-label="play/pause animation"><span class="visually-hidden">automatisch abspielen</span>
    <svg viewBox='0 0 200 200'>
      <rect width='200' height='200' fill='var(--fill)' />
      <path d='M60,30 l90,70 -90,70z' fill='white' stroke='white' stroke-width='30' stroke-linejoin='round' />
    </svg>
    <svg viewBox='0 0 100 100'>
      <rect width='100' height='100' fill='var(--fill)' />
      <path d='M30,20 L30,80 M70,20 L70,80' fill='none' stroke='white' stroke-width='25' stroke-linecap='round'/>
     </svg>
  </button>
  <button type="button" value="shuffle"><span class="visually-hidden">shuffle</span>
    <svg viewBox='0 0 376 376'>
      <rect x='0' y='0' width='100%' height='100%' fill='var(--fill)' stroke='black' stroke-width='1'/>
      <path d='M376 280l-79 68v-45h-13c-42 0-73-19-98-44 10-12 19-23 27-34 2-3 4-5 6-7 19 19 39 33 66 33h13v-38L376 280zM0 129h39c25 0 44 12 62 29 3-4 6-8 9-12 7-10 15-20 23-30 -25-23-55-40-95-40H0V129zM297 28v45h-13c-69 0-108 51-143 97 -31 41-58 76-101 76H0v53h39c69 0 108-51 143-97 31-41 58-76 101-76h13v38l79-68L297 28z' fill='white'/>
    </svg>
    <svg viewBox='0 0 100 100'>
      <rect width='100' height='100' fill='var(--fill)' />
      <path d='M30,20 L30,80 M70,20 L70,80' fill='none' stroke='white' stroke-width='25' stroke-linecap='round'/>
    </svg>
  </button>               
  <button type="button" value="prev"><span class="visually-hidden">vorheriges Bild</span>
    <svg width='100' height='200' viewBox='0 0 100 200' xmlns='http://www.w3.org/2000/svg'>
      <path id='left' d='M90,10 l-80,90 l80,90' fill='var(--fill)' stroke='var(--stroke)' stroke-width='15' stroke-linecap='round'/>
    </svg>
  </button>
  <button type="button" value="next"><span class="visually-hidden">nächstes Bild</span>
    <svg width='100' height='200' viewBox='0 0 100 200'>
      <path id='right' d='M10,10 l80,90 l-80,90' fill='var(--fill)' stroke='var(--stroke)' stroke-width='15' stroke-linecap='round'/>
    </svg>
  </button>
</dialog> 

</body>
</html>