Ich habe nichts gegen Plugins, im Gegenteil, ich programmiere manchmal sogar selbst welche, wie hier z.B. mein Essential SEO Plugin. Und sie können einem die Arbeit super vereinfachen, aber manchmal wollen sie einfach zu viel, weil sie zu generisch sind. Es braucht also ein wenig Kenntnis darüber, was man mit einer Lightbox machen will, um sie schlank zu halten. Zudem gibt es einige Details in meiner Lösung, auf die ich stolz bin, und einen ehrlichen Nachtrag zur Frage, was eigentlich genau von mir stammt.
Plugin nutzen oder selber programmieren?
Ein Lightbox-Plugin ist in WordPress schnell installiert. Es stehen auch viele Lösungen zur Auswahl. Funktional machen sie alle prinzipiell das Gleiche. Sie überbieten sich aber gerne mit Features – und beim Styling bzw. dem Anpassen an das eigene Design kommt man trotzdem schnell an Grenzen. Das liegt in der Natur der Sache: So ein Lightbox-Plugin muss generisch sein, weil man nicht ganz genau weiss, was der einzelne Nutzer damit anzeigen will, und der Markt sagt, dass am besten «alles» unterstützt werden muss. Was dabei fast immer fehlt, ist die Limitierung, die bewusste Beschränkung – ob man überhaupt «alles» unterstützen muss. Aus meiner Erfahrung heraus fragen sich aber genau das die wenigsten Anbieter. Und vor allem: Einmal installiert, lädt die Lightbox meist auf jeder Seite mit – auch dort, wo es gar nichts für sie zu öffnen gibt. Allerdings muss man eins hervorheben: Will man schnell eine funktionale Lightbox, dann ist ein Lightbox-Plugin eine gute und richtige Wahl. Unabhängig von meiner Kritik.
Alles von Grund auf selbst zu schreiben, wäre auf der anderen Seite genauso unnötig gewesen. Vor allem, wenn für ein gelöstes Problem schon gute, freie Grundlagen bestehen. Ein Bild oder anderen Inhalt öffnen, den Hintergrund abdunkeln, alles wieder schliessen: Das kennt man, und es funktioniert fast überall mit sehr ähnlichem Code. Genau aus diesem Grund habe ich den Mittelweg genommen. Ich habe eine schlanke, frei verfügbare Basis ausgewählt, die genau das kann und sonst nichts, und sie auf meinen Bedarf zugeschnitten. Damit habe ich mir keinen Plugin-Ballast an Bord geholt, das Rad musste ich aber auch nicht neu erfinden – ich habe nur die Teile ergänzt oder verändert, die ich wirklich wollte oder die es noch nicht gab.
Die Teile, die dazukamen
Die ausgewählte Lightbox-Basis konnte das Wesentliche, was man von ihr erwartet. Was ihr fehlte, waren die Dinge, die man «heute» von einer Lightbox erwartet – und genau die habe ich ergänzt.
Swipe und SVG-Icons
Am wichtigsten war, die Bedienung per Swipe, also per Wischen, hinzuzufügen. Denn auf dem Smartphone, oder besser: auf einem Touch-Screen, blättert man durch eine Galerie, indem man mit dem Finger zieht – man wischt. Das hast du bestimmt selbst schon fast automatisch gemacht, statt zu versuchen, einen kleinen Pfeil mit dem Finger, mit einer Berührung, zu treffen. Die Pfeile muss die Lightbox trotzdem weiterhin mit der vollen Funktionalität anbieten, denn damit sind dann alle Geräte und Bedienungsmöglichkeiten abgedeckt. Dazu kamen SVG-Icons zum Schliessen und Blättern, welche zudem auf jedem Display gestochen scharf bleiben und aus meinem eigenen Branding bzw. aus meinem Theme sind.
Pinch-Zoom
Ein weiteres Highlight, das oft fehlt: Pinch-Zoom, um sich mit zwei Fingern ein Detail heranzuholen. Zum Hintergrund der Funktion: Ich sehe immer wieder Nutzer, die den Pinch-Zoom in einer Lightbox verwenden wollen – und dann kann die Lightbox meist nicht mithalten.
Der in dieser Lightbox verwendete Pinch-Zoom-Ansatz funktioniert in der Praxis gut, aber er ist auch nicht perfekt. Dieses Thema vollumfänglich abzudecken, ist nach meinem aktuellen Wissensstand in diesem Rahmen fast unmöglich. Es wird immer ein Kompromiss bleiben, da sich der Pinch-Zoom mit dem nativen Zoom des Browsers verheddern kann. Zu diesem Teil muss ich später ohnehin noch etwas sagen.
Update 10.07.2026: Der Kompromiss war am Ende doch keiner. Ich habe mir den Pinch-Zoom noch einmal vorgenommen, weil mich das Verheddern mit dem nativen Zoom nicht losliess – und das Problem lag gar nicht dort, wo ich es vermutet hatte.
Der übernommene Code unterbindet den seiteneigenen Zoom des Browsers nirgends, er lässt ihn schlichtweg laufen. Auf dem Smartphone zoomte man deshalb nicht das Bild, sondern das ganze Dokument – Pfeile, Schaltflächen und alles andere gleich mit. Und sobald das Bild aus dem Sichtfeld geschoben wurde, fiel die Galerie auseinander. Das ist ausdrücklich keine Kritik an litelight: Der Code ist frei, er hat mir diese Funktion überhaupt erst zur Verfügung gestellt, und auf der Demo-Seite des Projekts lässt sich genau dasselbe beobachten. Der Punkt fehlte dort einfach.
Die Lösung ist unspektakulär, sobald man weiss, wo man ansetzen muss. Die Lightbox beansprucht die Geste jetzt für sich, statt sie sich mit dem Browser zu teilen: Sie schaltet die nativen Touch-Gesten in ihrem Overlay ab, fängt zusätzlich jene Zoom-Ereignisse ab, die nur der Safari-Browser (auf dem iPhone) verschickt, und hält das gezoomte Bild innerhalb seiner eigenen Grenzen. Bei dieser Gelegenheit habe ich den Lightbox-Aufbau von einem scrollbaren Behälter zu einem Flexbox-Container verändert – womit auch das Schliessen-Kreuz aufgehört hat, mit der Bildhöhe zu wandern. Was ich also bereits zuvor schon einmal behoben hatte, trat damit zunächst wieder auf und musste erneut behoben werden.
Ein hübsches Rätsel gab es obendrauf. Auf dem iPhone blieben nach dem Zoom und dem darauf folgenden Weiterblättern Geisterbilder stehen, sauber gestapelt, obwohl im Dokument nie mehr als ein einziges Bild existierte. Die Erklärung lag tiefer als im HTML: Safari (auf dem iPhone) legt für ein Element mit aktiver transform-Eigenschaft eine eigene Ebene auf der Grafikkarte an – und räumt sie nicht zuverlässig weg, wenn das Element verschwindet, während die Transformation noch anliegt. Die Eigenschaft ganz zu löschen, statt sie auf ihren Ausgangswert zurückzusetzen, genügte als Lösung.
Jetzt zoomt der Pinch-Zoom also genau das, was er zoomen soll: nur das Bild.
Probier es aus!
Das sind die Teile, die man sieht und anfasst. Die, auf die ich eigentlich stolz bin, sieht man nicht – sie stecken darunter.
Aber damit genug beschrieben für den Moment – probier es selbst im Lightbox-Demo aus.
Lightbox-Demo
Öffne ein Bild, klicke dich durch die Galerie oder navigiere mit der Tastatur durch sie hindurch (mit den Pfeiltasten, via Tab-Taste und Enter). Wenn du einen Touchscreen hast, wische durch die Bilder und zieh dir mit zwei Fingern ein Detail heran.




Dieselbe Lightbox wird übrigens auch in meinen Bildbeiträgen verwendet – zum Beispiel in meinem Beitrag über den Tag, an dem ich Motorrad-Model war.
Die Details, die zählen
Hier kommt das, was man nicht unbedingt direkt sieht – und was für mich den Unterschied macht. Erinnerst du dich an den einen Plugin-Kritikpunkt von vorhin, dass so ein Plugin, einmal installiert, auf jeder Seite mitlädt? Genau das wollte ich nicht.
Hinweis: Die Code-Beispiele in diesem Beitrag sind zur Erklärung vereinfacht – sie sind nicht direkt einsatzbereit oder vollständig.
Der Ladeprozess
Meine Lightbox lädt nur dort, wo es wirklich etwas zu öffnen gibt. Skript und Styles sind zwar registriert, aber beim Rendern jeder Seite prüft ein Filter, ob überhaupt eine Galerie mit den eigenen verlinkten Mediendateien oder ein so verlinktes Bild auf der aktuellen Seite vorkommt – und erst dann nimmt der Ladeprozess seinen Lauf.
function yourtheme_lightbox_maybe_enqueue( $block_content, $block ) {
if ( ! is_singular() ) {
return $block_content;
}
if ( in_array( $block['blockName'], array( 'core/image', 'core/gallery' ), true )
&& str_contains( $block_content, '<a ' ) ) {
wp_enqueue_style( 'yourtheme-lightbox' );
wp_enqueue_script( 'yourtheme-lightbox' );
}
return $block_content;
}
add_filter( 'render_block', 'yourtheme_lightbox_maybe_enqueue', 10, 2 );
Der Check auf das <a -Tag ist dabei der Trick. Kein mit der eigenen Mediendatei verlinktes Bild, keine so verlinkten Bilder in einer Galerie – und es lädt kein einziges Byte Lightbox-CSS oder JavaScript.
Zudem wird das JavaScript, wenn es denn nötig wird, mit defer und erst am Seitenende der Website geladen, es blockiert also nichts und lädt parallel zu anderen Inhalten und in der richtigen Reihenfolge mit anderen JavaScripten.
Dass dieser einfache Check auf ein <a>-Tag nicht weiter abgesichert ist, hat mit Haltung zu tun.
Denn bei mir verlinkt ein Bild innerhalb eines Beitrags ausschliesslich auf seine eigene Mediendatei – also genau den Fall, für den die Lightbox da ist. Ein Bild als Aufhänger für einen anderen Link, etwa auf ein PDF oder eine Unterseite, kommt bewusst nicht vor: Ein verlinktes Bild ist für die Barrierefreiheit eine schwache Wahl, dort sollte ein beschrifteter Text-Link oder Button verwendet werden, den auch ein Screenreader ansagen kann. Und warum Barrierefreiheit gerade jetzt relevanter wird, kannst du im Beitrag zu «Weniger Code, mehr Barrierefreiheit» nachlesen.
Man könnte den Filter natürlich genauer machen und zusätzlich prüfen, ob das Link-Ziel überhaupt eine Bilddatei ist – etwa anhand der Dateiendung. Aber das verkompliziert die Sache wieder, und diese Genauigkeit brauche ich gar nicht: Solange ein Bild-Link bei mir immer nur eines bedeutet – Lightbox –, reicht der schlichte Check auf das <a>-Tag.
Die Übersetzungen und Barrierefreiheit
Und wenn die Lightbox dann lädt, soll sie es richtig machen. Die Beschriftungen – Schliessen, Weiter, Zurück – kommen deshalb nicht aus dem JavaScript, sondern aus PHP, wo man sie mithilfe der WordPress Prozesse übersetzen kann. In meinem Theme ist z.B. Englisch die Grundsprache, Deutsch und Schweizerdeutsch liegen als Übersetzungen bei.
wp_localize_script( 'yourtheme-lightbox', 'yourthemeLightbox', array(
'close' => __( 'Close', 'yourtheme' ),
'next' => __( 'Next', 'yourtheme' ),
'prev' => __( 'Previous', 'yourtheme' ),
'loading' => __( 'Loading…', 'yourtheme' ),
) );
Das JavaScript bekommt diese Beschriftungen weitergereicht und hält nur für den Notfall ein Fallback-Objekt bereit, falls doch einmal nichts ankommt. Zusammen mit den Icons sind sie in einem Options-Objekt gespeichert, das die Lightbox an jede Instanz weitergeben kann.
const labels = ( typeof yourthemeLightbox !== 'undefined' ) ? yourthemeLightbox : {
close: 'Schliessen',
next: 'Weiter',
prev: 'Zurück',
loading: 'Laden…',
};
// SVG_CLOSE, SVG_PREV, SVG_NEXT: Inline-SVG-Icons aus dem Theme (aria-hidden)
const options = {
closeBtnCaption: labels.close,
nextBtnCaption: labels.next,
prevBtnCaption: labels.prev,
loadingCaption: labels.loading,
closeBtnIcon: SVG_CLOSE,
prevBtnIcon: SVG_PREV,
nextBtnIcon: SVG_NEXT,
captionAttribute: 'data-caption',
};
Der Grund dahinter ist die Barrierefreiheit. Die Buttons tragen nur ein SVG-Icon, keinen sichtbaren Text, und das Icon selbst ist für den Screenreader ausgeblendet. Vorgelesen wird stattdessen die Beschriftung, die als aria-label auf dem Button sitzt – und weil dieser Text aus PHP kommt, ist er übersetzt und in derselben Sprache wie der Rest der Seite, statt eines hartcodierten englischen «Close», das direkt aus dem JavaScript kommen würde.
In meinem Theme verwende ich dazu Übersetzungsdateien, so wie es korrekt ist. Wer umfassendere Mehrsprachigkeit braucht, stösst aber mit diesen Übersetzungsdateien und deren Verwaltung an Grenzen. Daher möchte ich erwähnen, dass man dieselben Texte auch direkt in WordPress verwalten und übersetzen könnte, etwa mit einem Plugin wie Polylang. Es bringt allerdings eine Abhängigkeit und einen Umbau mit sich und das führt hier zu weit; mir geht es nur darum zu zeigen, dass es diese Möglichkeit gibt.
Die Bildunterschrift
Auch die Bildunterschrift soll die Lightbox von allein richtig treffen. Sie nimmt zuerst die figcaption, die im Block ohnehin schon steht – und wenn keine da ist, den Alternativ-Text (Alt-Text).
function applyCaptionData( link ) {
const figure = link.closest( 'figure' );
if ( ! figure ) { return; }
// Zuerst die figcaption des Bildes …
const caption = figure.querySelector( 'figcaption' );
if ( caption && caption.textContent.trim() ) {
link.dataset.caption = caption.textContent.trim();
return;
}
// … sonst der Alt-Text
const img = link.querySelector( 'img' );
if ( img && img.alt.trim() ) {
link.dataset.caption = img.alt.trim();
}
}
So gibt es im Zweifel eine sinnvolle Bildunterschrift statt gar keiner, und ich muss den Text nicht ein zweites Mal pflegen. Zudem sollte genau dieser Alt-Text wieder bezogen auf die Barrierefreiheit zwingend bei begleitenden Bildern eines Beitrags gesetzt sein. Er muss das Bild beschreiben, dafür ist der Alt-Text da.
Ein weiteres Detail hängt direkt an genau dieser Bildunterschrift, die bei WordPress als figcaption unter dem verlinkten Bild steht, nicht im Link selbst. Wenn diese Bildunterschrift dann auch noch via CSS über dem Bild positioniert ist, sie darüber liegt, ginge ein Klick oder Tap deshalb ins Leere – obwohl es so aussieht, als gehöre der Teil zum Bild. Dann ist eine Zeile CSS nötig, um den Link unter der Bildunterschrift freizugeben, man reicht ihn sozusagen durch.
figcaption {
pointer-events: none;
}
Die Struktur
Und zuletzt geht es an die Struktur.
Mit dem Ladeprozess wurde sichergestellt, dass JavaScript und CSS nur geladen werden, wenn es auch wirklich Elemente dafür gibt. Jetzt muss sichergestellt werden, dass eine Instanz der Lightbox für diese Elemente richtig initialisiert wird. Und diese finale Initialisierung passiert in reinem JavaScript, ohne jQuery und ohne eine einzige zusätzliche Abhängigkeit neben der Lightbox-Basis.
Jede Galerie bekommt eine eigene Lightbox, damit man in ihr blättern kann. Mehrere Galerien auf der gleichen Seite lassen sich dadurch in ihrem Rahmen durchblättern, ohne dass sie sich mit einer anderen Galerie und deren Lightbox vermischen würden. Jedes einzeln stehende Bild bekommt ebenfalls eine Lightbox für sich, aber ohne Pfeile, weil es dort nichts zu blättern gibt.
// Eine Instanz pro Galerie – mit Navigation
document.querySelectorAll( '.wp-block-gallery' ).forEach( ( gallery ) => {
const links = gallery.querySelectorAll( 'a' );
if ( links.length ) {
links.forEach( applyCaptionData );
new SimpleLightbox( Object.assign( {}, options, { elements: links } ) );
}
} );
// Eine Instanz pro einzeln stehendem Bild – ohne Navigation
document.querySelectorAll( '.wp-block-image a' ).forEach( ( link ) => {
if ( ! link.closest( '.wp-block-gallery' ) ) {
applyCaptionData( link );
new SimpleLightbox( Object.assign( {}, options, { elements: [ link ] } ) );
}
} );
Das Styling
Und dann noch ein sichtbares Detail, das eigentlich nur auffällt, wenn es fehlt. Im Original sitzt der Schliessen-Button an der oberen rechten Ecke des Bildes und wird relativ zur Bildbreite positioniert – in em. Die Folge: Bei jedem unterschiedlich breiten Bild springt das X an eine andere Stelle. Ich zentriere das Bild stattdessen im Fenster und verankere den Button an einer festen Position, unabhängig davon, wie breit das Bild gerade ist:
// Bild im Viewport zentrieren, statt es die Breite bestimmen zu lassen
.slbContentOuter {
width: 100%;
}
.slbContent {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
// Schliessen-Button an fester Stelle – rem statt em, springt nicht mit dem Bild
.slbCloseBtn {
right: -0.25rem;
}
Das ist die Lightbox, auf die ich stolz bin. Bleibt die Frage, die ich am Anfang versprochen habe – was davon eigentlich von mir stammt.
Ehrlich forken
Die Basis, die ich gewählt habe, heisst simpleLightbox – und ich benutze sie schon seit vielen Jahren. Was ich an ihr schätze: das schlichte, zurückhaltende Design, die übersetzbaren Texte und dass sie einzelne Bilder per JavaScript zu einer Galerie zusammenfassen kann. Sie ist ausserdem schlank und funktioniert einfach auch nach Jahren noch.
Sie stammt von Damir Brekalo (dbrekalo) und steht unter der MIT-Lizenz. Das ist mein Ausgangspunkt. Warum es seit Jahren keine Updates mehr gab und warum inzwischen die Demo-Site auch nicht mehr korrekt geht, kann ich leider nicht beantworten. Deshalb habe ich sie schon vor längerem auf modernes JavaScript (ES6) umgeschrieben und neu um die Teile ergänzt, von denen ich vorher schrieb – den Swipe, die SVG-Icons, das Zusammenspiel mit den WordPress-Blöcken und das verbesserte Styling.
Bleibt der Pinch-Zoom, zu dem ich noch etwas sagen wollte. Der stammt nicht von mir. Den Code dafür habe ich aus einer zweiten, ebenfalls freien Lightbox übernommen – nicht als lose Inspiration, sondern als echte Übernahme. Unter der MIT-Lizenz ist das völlig in Ordnung, solange man sagt, woher es kommt. Und genau da wurde es kurz spannend, als ich anfing, alles für diesen Beitrag zusammenzutragen.
Ich wusste, dass zwei Bibliotheken im Spiel waren. Nur welche die zweite war, hatte ich vergessen – die Identität war schlicht verloren gegangen. Wiederhergestellt hat sie mein eigener Issue-Tracker: Ich hatte es dort festgehalten, dass es litelight von Byron Johnson (byronjohnson) ist, ebenfalls MIT. Der Abgleich der beiden Codes hat es dann eindeutig bestätigt.
Diese Herkunft nehme ich ernst – im Code und hier im Beitrag. Wer die Basis anschauen will: Ich habe sie als archiviertes Referenz-Repo offengelegt, nur die «neue» Bibliothek, meine Version, für sich, mit einer MIT-Lizenz, die beide Herkünfte sauber ausweist. Der Integrationscode aus diesem Beitrag ist bewusst nicht dabei – der ist an mein Theme gebunden und nicht Teil des Repos.
Seit dem Update vom 10.07.2026 hat sich diese Herkunft übrigens verschoben, ohne dabei kleiner zu werden. Die Mechanik des Pinch-Zooms – wie aus dem Abstand zweier Finger eine Skalierung wird und wie diese als Transformation auf dem Bild landet – ist unverändert die von litelight. Dazugekommen ist die Antwort auf die Frage, die dort offengeblieben war: dem Browser die Geste abzunehmen, damit er nicht gleichzeitig die ganze Seite mitzoomt. Dazu die Grenzen, in denen sich ein gezoomtes Bild bewegen darf, und der Umbau der Struktur darunter.
Die Versuchung ist gross, die Zeile im Quellcode zu streichen, die auf litelight verweist. Ich habe aber das Gegenteil gemacht – sie steht unverändert dort. Eine Herkunft verschwindet nicht dadurch, dass man den geerbten Code weiterbringt, sonst wäre jeder Fork nach der zweiten Änderung plötzlich ein Eigenbau. Eine Attribution ist keine Momentaufnahme der Anteile. Sie ist eine Aussage darüber, worauf etwas steht.
Und weil Geben in beide Richtungen gehen darf, habe ich den Fund dem litelight-Projekt zurückgemeldet. Es ist derselbe Code, ohne den es diese Funktion in meiner Lightbox nie gegeben hätte.
Vielleicht inspiriert dich die Idee dieser Lightbox für WordPress auch, und natürlich darfst du gerne forken.
Und für mich heisst ehrlich forken genau das: eine gute, freie Grundlage nehmen, sie zu seiner eigenen machen – und trotzdem klar benennen, was von wem stammt.
FAQ
Nein. Ein Plugin ist der schnelle Weg und für viele Situationen die richtige Wahl. Wer aber weiss, was er genau zeigen will, kommt mit einer schlanken, selbst zugeschnittenen Basis ohne Ballast ans Ziel. Mehr dazu unter Plugin nutzen oder selber programmieren?.
Bei Plugins, welche nicht wirklich in die Tiefe gehen, oft ja – auch dort, wo es gar nichts zu öffnen gibt. Meine Lösung lädt nur, wenn wirklich ein verlinktes Bild oder eine Galerie vorkommt, sonst kein einziges Byte. Wie das funktioniert, steht unter Der Ladeprozess.
Sie kann es sein, wenn man es richtig löst: übersetzte Beschriftungen kommen als aria-label aus PHP, Icons für den Screenreader werden ausgeblendet, Bildunterschriften kommen aus figcaption oder dem Alt-Text. Mehr unter Die Übersetzungen und Barrierefreiheit.
Unter einer freien Lizenz wie MIT ja – solange man klar benennt, was von wem stammt. Genau so bin ich mit der Basis und dem übernommenen Pinch-Zoom-Code umgegangen. Mehr unter Ehrlich forken.

Kommentare