Eine goldene Skulptur

Wie man aus basicLightbox eine richtige Galerie macht

| Javascript   Webdesign   Programmierung  

Vor kurzem war ich auf der Suche nach einem simplen Lightbox-Plugin. Dabei bin ich über basicLightbox gestolpert und war begeistert! Leider hat das Plugin keine native Galerie-Funktionalität. In diesem Artikel zeige ich dir Schritt für Schritt, wie man diese nachrüstet..

Wie bereits erwähnt, basiert meine Lösung für eine Galerie-Erweiterung auf Tobias Reichs JavaScript-Plug-in "basicLightbox". Ich habe mich für basicLightbox entschieden, weil es mit nur 4 KB un-gzippter Dateigröße das kleinste Plug-in ist, das ich finden konnte. Außerdem ist dieses Plug-in reines Javascript ohne Abhängigkeiten oder Bibliotheken, was ich sehr schätze. Klicke auf die Bilder rechts neben diesem Text für ein Beispiel der Galerie-Funktionalität.

Setup

Um basicLightbox einzurichten, musst du nur zwei Dateien in dein Projekt einbinden:

<link href="/css/basicLightbox.min.scss" rel="stylesheet">  
<script src="/js/basicLightbox.min.js"></script> 

Danach kann es losgehen. Das Besondere an basicLightbox ist, dass es keine automatische Bindung an irgendwelche Elemente oder andere Spezialfunktionalitäten hat. Du kannst ihm im Grunde nur sagen: "Bitte erstelle eine Lightbox mit einem von mir bereitgestellten Inhalt und öffne sie dann". Im Code würde das so aussehen:

import * as basicLightbox from 'basiclightbox'

const instance = basicLightbox.create(
  `//my amazing content`, 
  { options : 'amazing'}
);

instance.show();

Das macht das Plug-in zwar unglaublich vielseitig, bedeutet aber auch: Wenn du in der Lage sein willst, auf ein Bild zu klicken und die Lightbox soll sich öffnen, musst du das selbst programmieren. Das (und einige Pfeile, um die Lightbox als Slideshow zu nutzen) werden wir im Folgenden tun.

Was wir brauchen

Lass uns zuerst über unsere Komponenten nachdenken. Wir brauchen

  1. Das Lightbox-Plugin
  2. Bilder oder Thumbnails zum Anklicken
  3. Eine Liste der besagten Bilder
  4. Ein Skript, um die Lightbox zu öffnen, wenn ein Thumbnail angeklickt wird
  5. Ein Skript, das zeigt, welcher Inhalt verwendet werden soll
  6. Inhalt, der in die Lightbox eingefügt werden soll (z.B. die große Version eines Bildes)
  7. Pfeile innerhalb der Lightbox, um zum vorherigen/nächsten Inhalt zu springen
  8. Ein Skript, das die Funktionalität der Pfeile steuert.

Weiter oben habe ich erklärt, wie man das Plug-in einbindet. Der nächste Schritt wären die Bilder. Dieses Beispiel basiert auf Kirby als Content-Management-System, aber es funktioniert auch mit anderen Lösungen. Wichtig ist jedoch, dass du die Bildausgabe direkt manipulieren kannst. Das heißt: wenn eine Tag-Gruppe wie

<figure>
    <img src="amazingImage.jpg" alt="I always forget to write alt">
    <figcaption>An amazing image</figcaption>
</figure

automatisch generiert wird, ohne dass du einige Attribute manipulieren kannst, musst du wahrscheinlich nach einem anderen Ansatz suchen. 😔

Thumbnails und Daten

Für alle anderen: Im Sinne des unobtrusive Javascript, werden wir unseren figure Tag mit data-attributes erweitern. Diese helfen dabei, Daten an JS-Eventlistener zu übergeben und können nach den Daten benannt werden, die sie enthalten. In unserem Fall wollen wir drei Dinge übergeben:

  1. Die URL des größeren Bildes
  2. Die Beschreibung des größeren Bildes (könnte die gleiche sein wie figcaption, aber vielleicht willst du etwas anderes darin haben)
  3. Einen Index
    Außerdem sollte dein figure-Tag einen Klassennamen haben. Das wird sich später als nützlich erweisen.

Es gibt mehrere Möglichkeiten, diese Werte in den Attributen zu übergeben. Die Chancen stehen gut, dass du auf eine Art "Foreach"-Schleife stoßen wirst. Hier ist ein Beispiel, wie ich es in Kirby gelöst habe (welches per PHP läuft):

<?php 
  $gallery_counter = 0;
  foreach ($images as $image) : ?>
  <figure class="gallery" 
        data-originalurl="<?= $image->url() ?>" 
        data-caption="<?=$image->caption()?>" 
        data-imgageindex="<?= $gallery_counter ?>">

        <img class="gallery-image" 
            src="<?= $image->thumb()->url() ?>" 
            alt="<?= $image->alt() ?>">

        <figcaption> <?= $caption ?></figcaption>

    </figure>
<?php gallery_counter++; ?>
<?php endforeach; ?>

Im Quellcode der Website (Apfel+U auf dem Mac) sollte jetzt Folgendes stehen:

<figure data-originalurl="https://some.url" data-caption="another caption" data-imgageindex="0">
    <img src="amazingImage.jpg" alt="I always forget to write alt">
    <figcaption>An amazing image</figcaption>
</figure>

Liste der Bilder

Da wir nun die Daten-erweiterten Figures auf unserer Seite haben, lass uns die Daten zusätzlich in eine Liste zu packen. Dafür werden wir ein 2d-Array (im Grunde ein Array, das Arrays speichert) verwenden. Jeder Index unseres Arrays steht für ein Bild und speichert dessen URL, Bildunterschrift und Index (obwohl man letzteren wahrscheinlich weglassen könnte, da sein Wert mit dem Index des äußeren Arrays identisch ist. Aber der Einfachheit halber lassen wir es drin). Theoretisch sieht die Struktur wie folgt aus:

let galleryImages =[
[image1url, image1caption, image1index],
[image2url, image2caption, image2index],
[image3url, image3caption, image3index]
];

Ich habe zwei Wege gefunden, dies zu erreichen: Entweder man füllt das JS-Array per PHP mithilfe des oben genannten foreach-Loops oder man sucht die Elemente nachdem die Seite geladen wurde via Javascript und fügt sie ins Array ein.

Hier die php-Lösung:

<script>
  const galleryList = [
    <?php foreach ($images as $image) : ?>
      ['<?= $image->url() ?>',
      '<?=$image->caption()?>',
       <?= $gallery_counter ?>],
    <?php endforeach;?>
  ];
</script>

Und hier ist die Javascript-Lösung. Wenn Du sie nutzt, binde sie am Ende der Seite ein! Wie Du siehst, können die Data-Attribute unserer figures über .dataset in Kombination mit dem Attributnamen per Javascript abgefragt werden.

let galleryList = [];
let i = 0;
document.querySelectorAll('.gallery').forEach((item) => {
    newlist[i] = [
      item.dataset.originalurl,
      item.dataset.caption,
      item.dataset.imgageindex,
    ]
    i++;
});

Beide Wege funktionieren. Um die Redundanz zu reduzieren, könntest du auch zuerst die Liste generieren und dann die figures aus ihren Daten rendern. Da ich hier jedoch mit automatisch generierten Thumbnails arbeite (zu sehen in der ersten foreach-Schleife bei image()->thumb()-url()), war der gezeigte Weg ein bequemerer Ansatz.

Die Lightbox (🎉)

Nun, da wir alles eingerichtet haben, können wir endlich die Lightbox öffnen.
Als Erstes brauchen wir eine Instanz der Lightbox. In diesem Beispiel erstellen wir einfach eine "leere" Instanz beim Pageload, aber du könntest die Erstellung auch an den ersten Klick auf ein Bild oder Ähnliches binden. Es liegt ganz bei dir.

// Creation of Lightbox instance, but don't show it yet

const instance = basicLightbox.create('', {
    className: 'lightbox',
});
// In order to change the content later, we need the DOM-element of the instance:
const elem = instance.element();

Wie du sehen kannst, ist das erste Argument in basicLightbox.create() ein leerer String. Das ist der Ort, an dem der Inhalt später landen wird. Das zweite Argument kümmert sich um die Optionen unserer Instanz. In diesem Fall geben wir ihr einfach den Klassennamen "lightbox". Um die Lightbox später zu modifizieren, müssen wir auf das DOM-Element/Knoten zugreifen, das zu unserer Instanz gehört. Dafür ist .element() da.

Als Nächstes ist der Event-Listener dran. Schließlich soll die Lightbox erscheinen, wenn wir auf ein Bild klicken. Hier ist, wie wir das machen:

//select all elements with the class .gallery
document.querySelectorAll('.gallery').forEach((item) => {
//add an eventlistener to each one that triggers the appearance of the lightbox on click
  item.addEventListener('click', (event) => {
    instance.show(); // this shows the lightbox
  });
});

Wenn du jetzt auf ein Bild klicken würdest, würdest du eine seltsame Mischung aus Freude und Enttäuschung empfinden. Die Lightbox wird angezeigt, aber es ist nichts drin. Lass uns das beheben. Wir können unserem const elem sagen, welches html angezeigt werden soll, indem wir einfach über das gute alte .innerHTML darauf zugreifen. Da wir wollen, dass der Inhalt eine größere Version unserer Thumbnail-Figur ist, können wir den Code wiederverwenden und ihn so in einen String einfügen:

let htmlValue =`
<figure id="lightboxContent" class="lightbox-figure">
  <img class="lightbox-img" src="">
  <figcaption></figcaption>
</figure>
`

Wie du sehen kannst, gibt es ein großes Problem: wir können nicht auf den Inhalt von src oder <figcaption> zugreifen. Deshalb machen wir aus htmlValue eine Funktion, die einen String mit dynamisch veränderten Teilen zurückliefert:

function lightboxHtml(imgUrl, imgcaption) {
  let htmlValue = `
    <figure id="lightboxContent" class="lightbox-figure">
    <img class="lightbox-img" src="${imgUrl}">
    <figcaption >${imgcaption}</figcaption>
    </figure>
    `;
  return htmlValue;
}

Falls du es noch nicht bemerkt hast, das ist der Teil, wo alles zusammenkommt: Wir verpacken alles, was wir haben, in eine weitere Funktion, aktualisieren die Event-Handler, um die Daten-Attribute unserer Thumbnail-Figuren als Argumente zu übergeben und voilà: die Lightbox zeigt, was sie zeigen soll. Hier ist der komplette Code:


function lightboxHtml(imgUrl, imgcaption) {
  let htmlValue = `
    <figure id="lightboxContent" class="lightbox-figure">
    <img class="lightbox-img" src="${imgUrl}"> //inject url
    <figcaption >${imgcaption}</figcaption> //inject caption
    </figure>
    `;
  return htmlValue;
}

function makeLightbox(url, caption, index) {
  elem.innerHTML = lightboxHtml(url, caption); // update content of lightbox according to the result of lightboxHTML()
  instance.show(); //show the lightbox
}

document.querySelectorAll('.gallery').forEach((item) => {
  item.addEventListener('click', (event) => {
    makeLightbox( //pass data-attributes of figure
      item.dataset.originalurl,
      item.dataset.caption,
      item.dataset.imgageindex,
    );
  });
});

Unsere Lightbox funktioniert! 🥳 Du hast dich vielleicht gefragt, warum wir das Index-Argument in unserer makeLightbox-Funktion übergeben. Nun, lass uns einen Blick auf unsere Liste der Anforderungen werfen, um festzustellen, was noch zu tun ist und es sollte offensichtlich werden:

  1. ~~Das Lightbox-Plugin ~~
  2. Bilder oder Thumbnails zum Anklicken
  3. Eine Liste der besagten Bilder
  4. Ein Skript, um die Lightbox zu öffnen, wenn ein Thumbnail angeklickt wird
  5. Ein Skript, das zeigt, welcher Inhalt verwendet werden soll
  6. Inhalt, der in die Lightbox eingefügt werden soll (z.B. die große Version eines Bildes)
  7. Pfeile innerhalb der Lightbox, um zum vorherigen/nächsten Inhalt zu springen
  8. Ein Skript, das die Funktionalität der Pfeile steuert.

Bevor wir in den nächsten Teil eintauchen, hier noch ein paar Stylings für die Lightbox, die ich nützlich fand:

.lightbox-figure {
  display: none;
  max-height: 85vh;
  max-width: 85vw;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s ease;
  figcaption {
    color: #fafafa;
  }
}

.lightbox-img {
  object-fit: cover;
  height: 100%;
  width: 100%;
  max-height: 100%;
  max-width: 85vw;
  max-height: 85vh;
}

Galerie-Funktionalität

Wie es scheint, bleibt uns nur noch, klickbare Pfeile in unsere Lightbox einzubauen und sie zum nächsten (oder vorherigen) Bild springen zu lassen. Zuerst fügen wir die Pfeile zu unserer lightboxHtml()-Funktion hinzu. Dafür werden wir Buttons verwenden. Da die Pfeile auf das vorherige/nächste Bild verlinken sollen, fügen wir der Funktion noch zwei weitere Argumente hinzu. Diese Argumente werden in das Data-Next-Attribut des Buttons injiziert:

function lightboxHtml(imgUrl, imgcaption, prevImg, nextImg) {
  let htmlValue = `
    <button data-next="${prevImg}" class="gallery-arrow gallery-arrow-prev">«</button>
    <figure id="lightboxContent" class="lightbox-figure">
    <img class="lightbox-img" src="${imgUrl}">
    <figcaption>${imgcaption}</figcaption>
    </figure>
    <button data-next="${nextImg}" class="gallery-arrow gallery-arrow-next">»</button>
    `;
  return htmlValue;
}
.gallery-arrow {
  top: 50vh;
  position: absolute;
  border: none;
  background: transparent;
  width: 48px;
  height: 48px;
  fill: white;
  opacity: 0.5;
  transition: all 0.3s ease;

  &-prev {
    left: 6vw;
  }
  &-next {
    right: 6vw;
  }
  &:hover {
    opacity: 0.8;
    transition: all 0.3s ease;
  }
}

Natürlich müssen wir auch unsere makeLightbox()-Funktion aktualisieren, da sie nun 4 statt 2 Variablen an lightboxHtml() übergeben muss. Im Grunde genommen wollen wir den vorherigen und nächsten Index in Bezug auf unser aktuelles Bild berechnen. Wir sollten bedenken, dass der erste Index ([0]) unser nächster Index ist, wenn das aktuelle Bild das letzte im Array ist und umgekehrt. Ein einfaches If-Statement sollte den Job machen:

function makeLightbox(url, caption, index){
  let nextIndex = index + 1;
  if (nextIndex > galleryList.length - 1){
    nextIndex = 0;
  }
  let prevIndex = index - 1;
  if (prevIndex < 0){
    prevIndex = galleryList.length - 1;
  }
  elem.innerHTML = lightboxHtml(url, caption, nextIndex, prevIndex);
  instance.show();
  initiateArrowEventlisteners();
}

Im Moment würde nichts passieren, wenn wir auf die Pfeile klicken, weil es noch keinen Event-Listener für sie gibt (obwohl die Funktion im obigen Code aufgerufen wird). Daher brauchen wir einen weiteren Event-Listener. Dies ist auch der Teil, wo unser Array galleryList ins Spiel kommt: wir können auf die Daten der nächsten/vorherigen Bilder zugreifen, indem wir einfach das Data-Attribut der Pfeil-Buttons verwenden:

//put the listener into a fuction in order to renew it after we clicked an arrow
function initiateArrowEventlisteners(){  
  document.querySelectorAll('.gallery-arrow').forEach((item) => {
    item.addEventListener('click', (event) => {
      makeLightbox(
          galleryList[item.dataset.next][0], //access first index of next image (url)
          galleryList[item.dataset.next][1], //access second index of next image (caption)
          galleryList[item.dataset.next][2] //access third indes of next image (index)
        );
    })
  })
}

Es gibt noch eine letzte Änderung, die wir vornehmen sollten. Unsere Pfeile sollten nicht angezeigt werden, wenn es nur ein Bild auf der Seite gibt, nicht wahr? Während eine If-Else-Anweisung eine durchaus praktikable Option ist, entscheiden wir uns für einen Bedingter (ternärer) Operator, um etwas Platz zu sparen.

${galleryList.length > 1 ? `<button data-next="${prevImg}" class="gallery-arrow gallery-arrow-prev">«</button>` : ''}

//our lightboxHtml()-function now looks like this:

function lightboxHtml(imgUrl, imgcaption, prevImg, nextImg) {
  let htmlValue = `
    ${galleryList.length > 1 ? `<button data-next="${prevImg}" class="gallery-arrow gallery-arrow-prev">«</button>` : ''}
    <figure id="lightboxContent" class="lightbox-figure">
    <img class="lightbox-img" src="${imgUrl}">
    <figcaption>${imgcaption}</figcaption>
    </figure>
    ${galleryList.length > 1 ? ` <button data-next="${nextImg}" class="gallery-arrow gallery-arrow-next">»</button>`: ''}
    `;
  return htmlValue;
}

Und das war's auch schon. Unsere Lightbox-Galerie ist eingerichtet und läuft. Ich hoffe, dass dieser Artikel hilfreich für dich war. Wie hat dir dieses Tutorial gefallen? Hast du etwas hinzuzufügen oder ein paar Effizienzsteigerungen? Bitte lass es mich in den Kommentaren wissen, wenn es Fragen oder Anregungen gibt. Außerdem kannst du diesen Artikel gerne teilen.

Viel Erfolg bei allem,
Don

P.S.: Unten findest du den fertigen Code. Ich habe noch einen kleinen Einblendeeffekt hinzugefügt.

//Create Lightbox instance on pageload but dont show it yet
const instance = basicLightbox.create('', {
  className: 'lightbox',
});

const elem = instance.element(); //this is the DOM-element of the instance

//Blueprint for the gallery inside the Lightbox.
function lightboxHtml(imgUrl, imgcaption, prevImg, nextImg) {
  let htmlValue = `
    ${galleryList.length > 1 ? `<button data-next="${prevImg}" class="gallery-arrow gallery-arrow-prev">«</button>` : ''}
    <figure id="lightboxContent" class="lightbox-figure">
    <img class="lightbox-img" src="${imgUrl}">
    <figcaption>${imgcaption}</figcaption>
    </figure>
    ${galleryList.length > 1 ? ` <button data-next="${nextImg}" class="gallery-arrow gallery-arrow-next">»</button>`: ''}
    `;
  return htmlValue;
}

// Fill the instance with content and show it
function makeLightbox(url, caption, index) {
  let nextIndex = index + 1
  if (nextIndex > galleryList.length - 1) {
    nextIndex = 0;
  }
  let prevIndex = index - 1
  if (prevIndex < 0) {
    prevIndex = galleryList.length - 1;
  }
  elem.innerHTML = lightboxHtml(url, caption, nextIndex, prevIndex);
  instance.show();
  animateFadein();
  initiateArrowEventlisteners();
}

function animateFadein() {
  var figure = document.getElementById('lightboxContent');
  figure.style.display = 'block';
  figure.clientHeight;
  figure.style.opacity = 1;
}

// Eventlistener for click events on gallery images/figures
document.querySelectorAll('.gallery').forEach((item) => {
  item.addEventListener('click', (event) => {
    makeLightbox(
      item.dataset.originalurl,
      item.dataset.caption,
      item.dataset.imgageindex,
    );
  });
});

//eventlistener for gallery arrows
function initiateArrowEventlisteners(){  
  document.querySelectorAll('.gallery-arrow').forEach((item) => {
    item.addEventListener('click', (event) => {
      makeLightbox(
          galleryList[item.dataset.next][0],
          galleryList[item.dataset.next][1],
          galleryList[item.dataset.next][2]
        );
    })
  })
}

// fill the galleryList
let galleryList = [];
let i = 0;
document.querySelectorAll('.gallery').forEach((item) => {
    newlist[i] = [
      item.dataset.originalurl,
      item.dataset.caption,
      item.dataset.imgageindex,
    ]
    i++;
});
<!doctype HTML>
<html>
<head>
<title>My superamazing lightbox test</title>
<link href="/css/basicLightbox.min.scss" rel="stylesheet">  
<script src="/js/basicLightbox.min.js"></script> 
</head>
<body>
<figure data-originalurl="https://some1.url" data-caption="caption" data-imgageindex="0">
    <img src="amazingImage1.jpg" alt="I always forget to write alt 1">
    <figcaption>An amazing image 1</figcaption>
</figure>

<figure data-originalurl="https://some2.url" data-caption="another caption" data-imgageindex="1">
    <img src="amazingImage2.jpg" alt="I always forget to write alt 2">
    <figcaption>An amazing image 2</figcaption>
</figure>

<figure data-originalurl="https://some3.url" data-caption="yet another caption" data-imgageindex="2">
    <img src="amazingImage3.jpg" alt="I always forget to write alt 3">
    <figcaption>An amazing image 3</figcaption>
</figure>
</body>
</html>
.lightbox-figure {
  display: none;
  max-height: 85vh;
  max-width: 85vw;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.5s ease;
  cursor: pointer;

  figcaption{
    color: #fafafa;
  }
}

.lightbox-img {
  object-fit: cover;
  height: 100%;
  width: 100%;
  max-height: 100%;
  max-width: 85vw;
  max-height: 85vh;
}

.gallery-arrow {
  top: 50vh;
  position: absolute;
  border: none;
  background: transparent;
  width: 48px;
  height: 48px;
  fill: white;
  opacity: 0.5;
  transition: all 0.3s ease;

  &-prev {
    left: 6vw;
  }
  &-next {
    right: 6vw;
  }
  &:hover {
    opacity: 0.8;
    transition: all 0.3s ease;
  }
}

Kommentare

Füge einen Kommentar hinzu:

Noch keine Kommentare vorhanden 😢