Lightbox
Eine Komponente für Bild- und Videogalerien im Vollbild, die Lightbox aus @reelkit/react-lightbox.
Funktionen
Installation
npm install @reelkit/react-lightbox @reelkit/react lucide-reactVergiss nicht, die Styles zu importieren:
import '@reelkit/react-lightbox/styles.css';Icons
Die Standard-Controls nutzen lucide-react für die Icons. Wenn du eine andere Icon-Bibliothek bevorzugst, gib mit renderControls und renderNavigation deine eigenen vor.
Schnellstart
Die Komponente LightboxOverlay zeigt Bilder im Vollbild an. Übergib ein Array von LightboxItem-Objekten und steuere die Sichtbarkeit über einen Index, der auch null sein kann.
import { useState } from 'react';
import { LightboxOverlay, type LightboxItem } from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';
const images: LightboxItem[] = [
{
src: 'https://example.com/image1.jpg',
title: 'Sunset',
description: 'Beautiful sunset over the ocean',
},
{
src: 'https://example.com/image2.jpg',
title: 'Mountains',
},
];
function App() {
const [index, setIndex] = useState<number | null>(null);
return (
<>
{images.map((img, i) => (
<img
key={i}
src={img.src}
onClick={() => setIndex(i)}
/>
))}
<LightboxOverlay
isOpen={index !== null}
images={images}
initialIndex={index ?? 0}
onClose={() => setIndex(null)}
/>
</>
);
}import { useState } from 'react';
import {
LightboxOverlay,
flipTransition,
lightboxFadeTransition,
lightboxZoomTransition,
slideTransition,
type LightboxItem,
} from '@reelkit/react-lightbox';
import type { TransitionTransformFn } from '@reelkit/react';
import '@reelkit/react-lightbox/styles.css';
const images: LightboxItem[] = [
{
src: '/cdn/samples/images/image-01.jpg',
title: 'Mountain River',
description: 'A beautiful mountain river flowing through the forest',
width: 1600,
height: 1000,
},
{
src: '/cdn/samples/images/image-02.jpg',
title: 'Snowy Peaks',
description: 'Majestic snow-capped mountains reaching for the sky',
width: 1000,
height: 1600,
},
{
src: '/cdn/samples/images/image-03.jpg',
title: 'Foggy Forest',
description: 'Misty morning in the dense forest',
width: 1600,
height: 900,
},
{
src: '/cdn/samples/images/image-04.jpg',
title: 'Ocean Waves',
description: 'Powerful ocean waves crashing against the rocky shore',
width: 900,
height: 1400,
},
{
src: '/cdn/samples/images/image-05.jpg',
title: 'Autumn Path',
description: 'A winding path through the autumn forest',
width: 1600,
height: 1067,
},
{
src: '/cdn/samples/images/image-06.jpg',
title: 'Coastal Cliffs',
description: 'Dramatic coastal cliffs overlooking the deep blue sea',
width: 1600,
height: 1067,
},
];
const transitions: { label: string; fn: TransitionTransformFn }[] = [
{ label: 'slide', fn: slideTransition },
{ label: 'fade', fn: lightboxFadeTransition },
{ label: 'flip', fn: flipTransition },
{ label: 'zoom-in', fn: lightboxZoomTransition },
];
export default function App() {
const [index, setIndex] = useState<number | null>(null);
const [transitionFn, setTransitionFn] = useState<TransitionTransformFn>(
() => slideTransition,
);
return (
<div style={{ padding: 16, background: '#f8fafc', minHeight: '100vh' }}>
{/* Transition picker */}
<div style={{ display: 'flex', gap: 6, marginBottom: 12 }}>
{transitions.map((t) => (
<button
key={t.label}
onClick={() => setTransitionFn(() => t.fn)}
style={{
padding: '6px 14px', borderRadius: 8, border: 'none', cursor: 'pointer',
fontSize: 13, fontWeight: 500,
background: transitionFn === t.fn ? '#6366f1' : '#e2e8f0',
color: transitionFn === t.fn ? '#fff' : '#334155',
}}
>
{t.label}
</button>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
{images.map((img, i) => (
<button
key={i}
onClick={() => setIndex(i)}
style={{
position: 'relative', aspectRatio: '4 / 3', borderRadius: 8,
overflow: 'hidden', border: 'none', padding: 0, cursor: 'pointer',
background: '#e2e8f0',
}}
>
<img
src={img.src}
alt={img.title}
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
/>
</button>
))}
</div>
<LightboxOverlay
isOpen={index !== null}
images={images}
initialIndex={index ?? 0}
onClose={() => setIndex(null)}
transitionFn={transitionFn}
/>
</div>
);
}Wie du eine Galerie als Diashow abspielst und das Original jedes Fotos speicherst, zeigt das Rezept „Galerie als Diashow“.
Video-Slides (optional)
Die Video-Unterstützung ist komplett optional und per Tree-Shaking entfernbar: Wer nur Bilder zeigt, zahlt keinen Byte mehr im Bundle. Importiere useVideoSlideRenderer und gib seine Rückgabewerte an LightboxOverlay weiter. Der Hook kümmert sich automatisch um Ladezustände, den Ton und den Lebenszyklus der Videos.
import {
LightboxOverlay,
useVideoSlideRenderer,
type LightboxItem,
} from '@reelkit/react-lightbox';
import '@reelkit/react-lightbox/styles.css';
const items: LightboxItem[] = [
{ src: '/photo.jpg', title: 'Photo' },
{
src: '/clip.mp4',
type: 'video',
poster: '/clip-thumb.jpg',
title: 'Video Clip',
},
];
function Gallery() {
const [index, setIndex] = useState<number | null>(null);
const isOpen = index !== null;
const { renderSlide, renderControls, SoundProvider } =
useVideoSlideRenderer(items);
return (
<SoundProvider>
{/* thumbnails… */}
<LightboxOverlay
isOpen={isOpen}
images={items}
initialIndex={index ?? 0}
onClose={() => setIndex(null)}
renderSlide={renderSlide}
renderControls={renderControls}
/>
</SoundProvider>
);
}So funktioniert es
- Der Hook gibt
SoundProviderzurück: Umschließe dein Overlay damit, damit das Stummschalten funktioniert - Videos starten automatisch (standardmäßig stumm), sobald die Slide aktiv wird
- Ein gemeinsames Video-Element wird über alle Slides wiederverwendet, damit der Ton auf iOS nicht abreißt
- Der Ton-Button erscheint auf Video-Slides von selbst, mit einem reaktiven Schalter für den Ton
- Elemente ohne
type: 'video'werden als Bilder gerendert (abwärtskompatibel)
Anpassung
Eigene Controls
Mit renderControls ersetzt du den Standard-Schließen-Button, den Zähler und den Vollbild-Schalter. Kombiniere dafür die exportierten Unterkomponenten:
import {
LightboxOverlay,
CloseButton,
Counter,
FullscreenButton,
} from '@reelkit/react-lightbox';
<LightboxOverlay
isOpen={isOpen}
images={images}
onClose={handleClose}
renderControls={({ onClose, activeIndex, count, isFullscreen, onToggleFullscreen }) => (
<div style={{ position: 'absolute', top: 12, left: 16, right: 16, display: 'flex', justifyContent: 'space-between', zIndex: 10 }}>
<Counter currentIndex={activeIndex} count={count} />
<div>
<FullscreenButton isFullscreen={isFullscreen} onToggle={onToggleFullscreen} />
<CloseButton onClick={onClose} />
</div>
</div>
)}
/>Eigenes Info-Overlay
Mit renderInfo ersetzt du den Standard-Verlauf mit Titel und Beschreibung, oder du übergibst renderInfo={() => null}, um ihn ganz auszublenden:
<LightboxOverlay
isOpen={isOpen}
images={images}
onClose={handleClose}
renderInfo={({ item, index }) => (
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: 16, background: 'linear-gradient(transparent, rgba(0,0,0,0.8))', color: '#fff', zIndex: 10 }}>
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
)}
/>Eigene Navigation
Mit renderNavigation ersetzt du die Standard-Pfeile für zurück und weiter:
<LightboxOverlay
isOpen={isOpen}
images={images}
onClose={handleClose}
renderNavigation={({ onPrev, onNext, activeIndex, count }) => (
<div style={{ position: 'absolute', bottom: 24, left: '50%', transform: 'translateX(-50%)', display: 'flex', gap: 12, zIndex: 10 }}>
<button onClick={onPrev} disabled={activeIndex === 0}>Prev</button>
<span>{activeIndex + 1} / {count}</span>
<button onClick={onNext} disabled={activeIndex === count - 1}>Next</button>
</div>
)}
/>Eigene Slide
Mit renderSlide gestaltest du den Inhalt einer Slide komplett selbst. Gib null zurück, um auf die Standard-Bild-Slide zurückzufallen:
<LightboxOverlay
isOpen={isOpen}
images={images}
onClose={handleClose}
renderSlide={({ item, index, size, isActive, onReady, onError }) => {
// Custom CTA on last slide
if (index === images.length - 1) {
return (
<div style={{ width: size[0], height: size[1], display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff' }}>
<h2>View all photos</h2>
</div>
);
}
return null; // default image slide
}}
/>Laden von Inhalten und Fehlerbehandlung
Die Lightbox verfolgt den Lade- und Fehlerzustand jeder Slide. Ein Spinner erscheint, solange der Inhalt lädt; ein Symbol für ein kaputtes Bild erscheint bei Medien, die nicht geladen werden konnten. Fehlerhafte URLs werden gemerkt, sodass ein erneuter Besuch den Fehler sofort zeigt, ohne es noch einmal zu versuchen.
Lebenszyklus-Callbacks
Wenn du renderSlide verwendest, rufst du diese Callbacks auf, um die Ladeanzeige zu steuern:
| Callback | Wann aufrufen |
|---|---|
onReady | Das Bild ist geladen oder das Video spielt. Setzt Lade- und Fehlerzustand zurück. |
onWaiting | Das Video puffert während der Wiedergabe. Zeigt die Ladeanzeige. |
onError | Der Inhalt konnte nicht geladen werden. Zeigt das Fehler-Overlay und merkt sich die URL als defekt. |
// Inside renderSlide — wire callbacks to your custom media
renderSlide={({ item, index, size, isActive, onReady, onWaiting, onError }) => (
<div style={{ width: size[0], height: size[1] }}>
{item.type === 'video' ? (
<video
src={item.src}
poster={item.poster}
autoPlay={isActive}
onCanPlay={onReady}
onWaiting={onWaiting}
onError={onError}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
) : (
<img
src={item.src}
onLoad={onReady}
onError={onError}
style={{ width: '100%', height: '100%', objectFit: 'contain' }}
/>
)}
</div>
)}Eigene Lade- und Fehleranzeige
Ersetze den Standard-Spinner und das Fehlersymbol durch eigene Komponenten:
<LightboxOverlay
isOpen={isOpen}
images={images}
onClose={() => setIsOpen(false)}
renderLoading={({ item, activeIndex }) => (
<div style={{
position: 'absolute', inset: 0, zIndex: 10,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#fff', fontSize: 14,
}}>
Loading image {activeIndex + 1}...
</div>
)}
renderError={({ item, activeIndex }) => (
<div style={{
position: 'absolute', inset: 0, zIndex: 10,
display: 'flex', flexDirection: 'column',
alignItems: 'center', justifyContent: 'center',
gap: 12, color: 'rgba(255,255,255,0.5)',
}}>
<span style={{ fontSize: 48 }}>!</span>
<span>Failed to load content</span>
</div>
)}
/>URL-State
Live-Demo ansehen →LightboxUrlOverlay ist eine eigene Komponente, deren Öffnungszustand in der Adressleiste steht. Erstelle mit useOverlayUrlState aus @reelkit/react einen Controller und übergib ihn als controller: Die Galerie öffnet sich selbst, wenn der Parameter eine Slide benennt, und schließt sich, wenn er verschwindet. Links lassen sich teilen, und der Zurück-Button schließt die Galerie, statt die Seite zu verlassen.
Eingebaute Schlüssel
Du kannst Slides mit einem eingebauten Schlüssel ansprechen: Übergib urlIndexKey (nach Position) oder urlStableIdKey (nach einer stabilen id) per Spread an den Controller. Beide werden von @reelkit/react re-exportiert. Mehr dazu im Guide zum URL-State und in der Core API.
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/react';
import { LightboxUrlOverlay } from '@reelkit/react-lightbox';
import { Link } from 'react-router-dom';
const photo = useOverlayUrlState({
param: 'photo',
...urlIndexKey(() => images.length),
});
// Opening is a link — the href is the open action. No open flag, no handler:
// the overlay reads the URL and opens itself.
{images.map((image, i) => (
<Link key={image.src} to={`?photo=${i}`}>
<img src={image.src} />
</Link>
))}
<LightboxUrlOverlay controller={photo} images={images} />Der Hook nimmt ein Options-Objekt und gibt einen UrlStateController zurück (mit set, index, value). Behalte ihn für die Steuerung per Code: set ist der Schreibzugriff auf unterster Ebene, den das Overlay intern nutzt (Slide-Wechsel, und set(null) zum Schließen). Damit steuerst du das Overlay auch per Code: set(index) öffnet es, genau wie das Navigieren zum Parameter. Zum Öffnen ist ein Link trotzdem die bessere Wahl: Der href lässt sich teilen, öffnet sich in einem neuen Tab, und der Zurück-Button schließt es wieder, alles ohne Handler.
Alle Optionen von useOverlayUrlState (param, adapter, codec, locator) findest du in der React-API-Referenz.
LightboxUrlOverlay selbst nimmt nur controller (erforderlich), ein optionales onClose und dazu jedes Darstellungs- und Verhaltens-Prop, das auch LightboxOverlay nimmt (images, ariaLabel, transitionFn, die Render Props und so weiter), aber kein isOpen.
- Das Öffnen kostet einen Eintrag im Verlauf. Das Blättern durch Slides ersetzt ihn, sodass hundert Wischbewegungen keinen einzigen hinzufügen: Ein Schritt zurück verlässt die Galerie immer. Zurück schließt; es blättert nicht durch die Fotos.
- Ein geteilter Link wie
?photo=3öffnet die Galerie an dieser Slide. Wird ein Link geschlossen, der mit der Seite ankam, entfernt das den Parameter an Ort und Stelle, statt von deiner Website weg zu navigieren. - Zurück schließt nur, wenn du aus der App heraus geöffnet hast: Der Link hat einen Eintrag angelegt, also führt Zurück wieder zur Galerie. Ein geteilter Link, der direkt in einem frischen Tab geöffnet wird, hat keinen Verlauf hinter sich, daher verlässt Zurück im Browser die Website; der Schließen-Button oder Escape entfernt den Parameter an Ort und Stelle und lässt dich auf der Galerie. Die mitgelieferten Router-Adapter bestätigen einen Link-Push auf derselben Seite; ein eigener Adapter, der nicht sagen kann, wie der Eintrag zustande kam, bekommt ebenfalls das Schließen an Ort und Stelle. Das hinterlässt eine Kopie der Seite im Verlauf: Ein Schritt zurück sieht dann aus, als wäre nichts passiert, aber es öffnet sich nichts wieder.
- Ein Parameter, der keine Slide benennt (ein veraltetes Lesezeichen, ein von Hand geänderter Wert), wird aus der URL entfernt, statt in der Adressleiste eine Slide zu behaupten, die sich nicht öffnen lässt.
In einer App mit Router übergibst du einen Adapter. Wer direkt in den Verlauf schreibt, lässt den Router mit einer veralteten Location zurück, und seine nächste Navigation verwirft den Parameter.
import { useReactRouterUrlAdapter } from '@reelkit/react/react-router-url-adapter';
const adapter = useReactRouterUrlAdapter();
const photo = useOverlayUrlState({
param: 'photo',
adapter,
...urlIndexKey(() => images.length),
});
<LightboxUrlOverlay controller={photo} images={images} />Öffnen ist ein Link. Weil in der URL steht, ob die Galerie offen ist, ist ein Vorschaubild ein ganz normaler Link ohne Klick-Handler, und das Verhalten des Browsers gibt es gratis dazu: in einem neuen Tab öffnen, die Adresse kopieren, beim Überfahren eine Vorschau sehen. In einer App mit Router nimmst du den Link des Routers, damit alles clientseitig bleibt.
import { Link } from 'react-router-dom';
// The href is the open action — no onClick, no open flag.
{images.map((image, i) => (
<Link key={image.src} to={`?photo=${i}`}>
<img src={image.src} />
</Link>
))}
<LightboxUrlOverlay controller={photo} images={images} />Nimm für teilbare Links eine stabile Identität. Der Index ist eine Position, daher öffnet ein gespeichertes ?photo=3 ein anderes Bild, sobald die Liste umsortiert wird. urlStableIdKey verwendet die stabile id jedes Elements und durchsucht die aktuelle Liste: Ein einziger Aufruf deckt den üblichen Fall ab.
const photo = useOverlayUrlState({
param: 'photo',
...urlStableIdKey({ items: () => images }),
});
<LightboxUrlOverlay controller={photo} images={images} />Übergib hashCodec: base64UrlCodec, um die id in der URL als base64url zu kodieren. Das ist eine umkehrbare Verschleierung, kein kryptografischer Hash.
Für ein anderes Feld als Schlüssel (einen slug) oder zum Nachladen eines endlosen Feeds mit locateAsync baust du codec/locator selbst:
const photo = useOverlayUrlState({
param: 'photo',
codec: { decode: (raw) => raw, encode: (id) => id },
locator: {
locate: (id) => images.findIndex((x) => x.slug === id),
identify: (index) => images[index].slug,
},
});
<LightboxUrlOverlay controller={photo} images={images} />Endlose oder seitenweise geladene Galerien. Das synchrone locate kann nur für bereits geladene Bilder antworten: Ein geteilter Link auf Bild 400 eines Feeds, der erst 20 geladen hat, findet nichts. locateAsync ist der Fallback und wird nur aufgerufen, wenn locate nichts findet.
Abkürzung
Ist die id des Elements dein Schlüssel? Dann spar dir den selbst geschriebenen Codec und Locator und übergib locateAsync direkt an urlStableIdKey({ items, locateAsync }) (es lädt nach, wenn nichts gefunden wird, und gibt dann den Index zurück). Die ausführliche Version unten ist für einen anderen Schlüssel oder für die volle Kontrolle gedacht.
const photo = useOverlayUrlState({
param: 'photo',
codec: { decode: (raw) => raw, encode: (id) => id },
locator: {
locate: (id) => images.findIndex((x) => x.id === id),
identify: (index) => images[index].id,
locateAsync: async (id) => {
const loaded = await loadById(id); // or loadUntil(id) — fetch just that one, or page up to it
if (!loaded) return null; // exhausted — link names no image
setImages(loaded); // commit — the overlay renders from this state
return loaded.findIndex((x) => x.id === id); // wherever it landed
},
},
});
<LightboxUrlOverlay controller={photo} images={images} />- Wie du nachlädst, entscheidest du: Lade zusammenhängende Seiten bis zum Ziel oder nur das eine Bild und hänge es an. Die URL verwendet die Identität, nicht die Position, daher gibt
findIndexzurück, wo auch immer das Element landet. - Solange
locateAsyncläuft, bleibt die Lightbox geschlossen und der Parameter unangetastet, damit der Deep Link das Laden übersteht.nulloder eine Ablehnung entfernt den Parameter. - Eine Antwort, die erst eintrifft, nachdem sich die URL geändert hat, nach dem Schließen oder nach dem Unmount, wird verworfen: Ein langsames Laden kann keine Slide öffnen, nach der niemand gefragt hat.
- Während des Wartens wird nichts gerendert; die Seite hat diesen Ladezustand ohnehin schon, also rendere dein eigenes Skeleton.
- Es gibt kein Timeout: Die Lightbox kann nicht wissen, wie lang die Galerie ist. Antworte mit
null, wenn keine Seiten mehr kommen, sonst bleibt das Overlay auf Dauer geschlossen. - Was
locateAsynczurückgibt, gilt: der Index der gerade geladenen Daten, unverändert übernommen, ohneimagesnoch einmal zu lesen.
Automatisches Weiterblättern
Setze autoAdvance, um die Galerie beim Öffnen als Diashow abzuspielen. Standardmäßig ist es aus, sodass eine bestehende Lightbox wie bisher rendert und sich wie bisher verhält.
import { LightboxOverlay } from '@reelkit/react-lightbox';
<LightboxOverlay
isOpen={index !== null}
images={images}
initialIndex={index ?? 0}
onClose={() => setIndex(null)}
autoAdvance
autoAdvanceInterval={4000}
/>;- Bilder bleiben für
autoAdvanceIntervalstehen (standardmäßig 5000 ms), gezählt ab dem Moment, in dem das Bild geladen ist, sodass ein langsames Bild nie übersprungen wird, bevor es jemand gesehen hat. Bei einem Bild, das nicht lädt, zählt die Zeit ab dem Fehler. - Videos laufen einmal, statt zu loopen, und es geht weiter, wenn sie enden. Mit
useVideoSlideRendererist das für dich verdrahtet; ein eigenesrenderSlideliestautoAdvanceaus seinen Props und meldet das Ende überonEnded. - Es wartet, solange die Maus auf einem Control ruht (einem Button, einem Link oder der Bildunterschrift), während einer Wischbewegung und solange die Seite verborgen ist, und läuft dann dort weiter, wo es aufgehört hat.
- Blätterst du von Hand (Pfeile, Tasten, Mausrad, Wischen), beginnt das Intervall der neuen Slide bei null; die Diashow bleibt an.
- Auf der letzten Slide hält es an, außer
loopist gesetzt. Der Play/Pause-Button in den Controls zeigt den Zustand und lässt die Betrachtenden umschalten.
Ein eigenes renderControls bekommt isAutoAdvancing und, nur wenn autoAdvance an ist, onToggleAutoAdvance. Rendere einen PlayPauseButton, wenn es vorhanden ist. Das Timing selbst übernimmt createSlideshowController aus @reelkit/core; im Guide zu Autoplay baust du dasselbe Verhalten in deinen eigenen Player ein, und die Core API beschreibt seine Methoden.
Download
Setze showDownload, um den Controls einen Download-Button hinzuzufügen. Er speichert das aktive Element von seiner downloadUrl, ersatzweise von src, unter seinem downloadName, ersatzweise unter dem letzten Abschnitt der URL.
import { LightboxOverlay, type LightboxItem } from '@reelkit/react-lightbox';
const images: LightboxItem[] = [
{
src: 'https://cdn.example.com/beach-1600.jpg',
// Save the original rather than the resized preview
downloadUrl: 'https://cdn.example.com/beach-original.jpg',
downloadName: 'beach.jpg',
},
];
<LightboxOverlay
isOpen={index !== null}
images={images}
initialIndex={index ?? 0}
onClose={() => setIndex(null)}
showDownload
/>;Die Datei wird geladen und gespeichert, weil ein Browser das Attribut download bei einem Link auf einen anderen Origin ignoriert und die Datei stattdessen öffnen würde. Schlägt das Laden fehl (meist ein Server auf einem anderen Origin, der keine CORS-Header sendet), öffnet sich die Datei in einem neuen Tab, wo man sie trotzdem speichern kann. Liefere die Dateien mit Access-Control-Allow-Origin aus, damit ein Klick zum Speichern reicht. Während ein Download läuft, ist der Button deaktiviert.
Ein eigenes renderControls bekommt isDownloading und, nur wenn showDownload an ist, onDownload. Rendere einen DownloadButton, wenn es vorhanden ist. Die Hilfsfunktion ist downloadFile aus @reelkit/core; mehr dazu im Guide zu Downloads.
API-Referenz
Props von LightboxOverlay
LightboxOverlayProps
| Prop | Typ | Standard | Beschreibung |
|---|---|---|---|
isOpen | boolean | Pflicht | Steuert die Sichtbarkeit der Lightbox. Soll die URL steuern, ob sie offen ist, nimm stattdessen die eigene Komponente LightboxUrlOverlay; siehe den URL-State weiter oben. |
images | LightboxItem[] | Pflicht | Array der anzuzeigenden Bilder |
ariaLabel | string | 'Image gallery' | Zugängliche Beschriftung des Dialogbereichs; Screenreader lesen sie vor, wenn sich die Lightbox öffnet |
initialIndex | number | 0 | Index des ersten Bildes |
transitionFn | TransitionTransformFn | slideTransition | Funktion für den Slide-Übergang. Importiere eine eingebaute (slideTransition, flipTransition, lightboxFadeTransition, lightboxZoomTransition) oder übergib eine eigene. Ohne Angabe gilt slideTransition. |
autoAdvance | boolean | false | Spielt die Galerie als Diashow ab. Siehe Automatisches Weiterblättern. |
autoAdvanceInterval | number | 5000 | Wie lange jedes Bild in einer Diashow stehen bleibt, in Millisekunden, gezählt ab dem Laden |
showDownload | boolean | false | Fügt einen Download-Button für das aktive Element hinzu. Siehe Download. |
apiRef | MutableRefObject<ReelApi> | - | Ref für den Zugriff auf die Reel-API |
renderControls | (props: ControlsRenderProps) => ReactNode | - | Eigene Controls, ersetzt den Standard-Schließen-Button, den Zähler und den Vollbild-Schalter |
renderNavigation | (props: NavigationRenderProps) => ReactNode | - | Eigene Navigation, ersetzt die Standard-Pfeile für zurück und weiter |
renderInfo | (props: InfoRenderProps) => ReactNode | - | Eigenes Info-Overlay, ersetzt den Standard-Verlauf mit Titel und Beschreibung. Gib null zurück, um es auszublenden. |
renderSlide | (props: SlideRenderProps) => ReactNode | null | - | Eigenes Rendering der Slide. Bekommt { item, index, size, isActive, autoAdvance, onReady, onWaiting, onError, onEnded }. Gib null zurück, um auf den Standard zurückzufallen. |
renderLoading | (props: { item: LightboxItem; activeIndex: number }) => ReactNode | - | Eigene Ladeanzeige, ersetzt den Standard-Spinner |
renderError | (props: { item: LightboxItem; activeIndex: number }) => ReactNode | - | Eigene Fehleranzeige, ersetzt das Standard-Fehlersymbol |
Props von LightboxUrlOverlay
LightboxUrlOverlayProps
Nimmt jedes Darstellungs- und Verhaltens-Prop von oben außer isOpen und ersetzt es durch einen controller. initialIndex wird hier ignoriert: Die Position des Controllers bestimmt die Slide, daher würde ein zusätzlich übergebener Wert bei jedem Öffnen überschrieben.
| Prop | Typ | Standard | Beschreibung |
|---|---|---|---|
controller | UrlStateController | Pflicht | Controller aus useOverlayUrlState. Seine Position entscheidet, ob das Overlay offen ist und welche Slide es zeigt; das Overlay schreibt beim Slide-Wechsel und beim Schließen über ihn zurück. |
Callbacks
| Prop | Typ | Beschreibung |
|---|---|---|
onClose | () => void | Wird aufgerufen, wenn sich die Lightbox schließt. Erforderlich bei LightboxOverlay (du steuerst, ob sie offen ist, also musst du das Schließen behandeln); optional bei LightboxUrlOverlay, wo die URL das Schließen steuert. Übergib es dort nur, um nach dem Schließen zu reagieren. |
onSlideChange | (index: number) => void | Wird nach einem Slide-Wechsel aufgerufen |
Reel-Props (durchgereicht)
Diese Props werden an die zugrunde liegende Komponente Reel weitergegeben. Ihr Typ ist ReelProxyProps, die Teilmenge der Props von Reel, die das Overlay durchreicht; typisiere damit einen Wrapper, der sie weitergibt.
| Prop | Typ | Standard | Beschreibung |
|---|---|---|---|
loop | boolean | false | Aktiviert die Endlosschleife |
enableNavKeys | boolean | true | Aktiviert die Tastaturnavigation |
enableWheel | boolean | true | Aktiviert die Navigation mit dem Mausrad |
wheelDebounceMs | number | 200 | Debounce-Dauer für das Mausrad (ms) |
transitionDuration | number | 300 | Dauer der Übergangsanimation (ms) |
swipeDistanceFactor | number | 0.12 | Schwelle für das Wischen (0-1) |
swipeToCloseDirection | 'up' | 'down' | 'up' | Richtung der Geste zum Schließen auf Mobilgeräten |
Typen
LightboxItem
interface LightboxItem {
src: string;
type?: 'image' | 'video'; // defaults to 'image'
poster?: string; // thumbnail for video items
title?: string;
description?: string;
width?: number;
height?: number;
downloadUrl?: string; // what the download button saves; defaults to src
downloadName?: string; // file name; defaults to the URL's last segment
}ControlsRenderProps
interface ControlsRenderProps {
item: LightboxItem;
activeIndex: number;
count: number;
isFullscreen: boolean;
isAutoAdvancing: boolean;
isDownloading: boolean;
onClose: () => void;
onToggleFullscreen: () => void;
onToggleAutoAdvance?: () => void; // only with autoAdvance
onDownload?: () => void; // only with showDownload
}NavigationRenderProps
interface NavigationRenderProps {
item: LightboxItem;
activeIndex: number;
count: number;
onPrev: () => void;
onNext: () => void;
}SlideRenderProps
interface SlideRenderProps {
item: LightboxItem;
index: number;
size: [number, number];
isActive: boolean;
autoAdvance: boolean; // a video should play once, not loop
onReady: () => void;
onWaiting: () => void;
onError: () => void;
onEnded: () => void; // a video played to its end
}InfoRenderProps
interface InfoRenderProps {
item: LightboxItem;
index: number;
}Unterkomponenten
Wiederverwendbare Unterkomponenten, mit denen du über renderControls eigene Controls zusammenstellst.
CloseButton
Standard-Schließen-Button mit X. Props (CloseButtonProps): onClick, optional className und style.
import { CloseButton } from '@reelkit/react-lightbox';
<CloseButton onClick={onClose} />Counter
Zähler für die Bilder als Pille, zeigt "1 / 3". Props (CounterProps): currentIndex (ab null gezählt), count, optional className und style.
import { Counter } from '@reelkit/react-lightbox';
<Counter currentIndex={activeIndex} count={count} />FullscreenButton
Schalter für das Vollbild (Icon Maximize/Minimize). Props (FullscreenButtonProps): isFullscreen, onToggle, optional className und style.
import { FullscreenButton } from '@reelkit/react-lightbox';
<FullscreenButton isFullscreen={isFullscreen} onToggle={onToggleFullscreen} />SoundButton
Schalter für den Ton auf Video-Slides (Icon Volume2/VolumeX). Ist in renderControls aus useVideoSlideRenderer automatisch enthalten. Für den eigenständigen Einsatz in eigenen Controls greifst du über useSoundState auf den Tonzustand zu. Props (SoundButtonProps): isMuted, onToggle, optional className und style.
import { SoundButton } from '@reelkit/react-lightbox';
import { useSoundState } from '@reelkit/react';
// Inside a component wrapped in SoundProvider:
function CustomControls({ onClose }) {
const soundState = useSoundState();
return (
<div>
<SoundButton
isMuted={soundState.muted.value}
onToggle={soundState.toggle}
/>
<button onClick={onClose}>Close</button>
</div>
);
}PlayPauseButton
Schalter für die Diashow (Icon Play/Pause, aria-pressed während der Wiedergabe). Props (PlayPauseButtonProps): isPlaying, onToggle, optional className und style. Die Standard-Controls zeigen ihn, wenn autoAdvance an ist.
import {
LightboxOverlay,
PlayPauseButton,
CloseButton,
} from '@reelkit/react-lightbox';
<LightboxOverlay
autoAdvance
renderControls={({ isAutoAdvancing, onToggleAutoAdvance, onClose }) => (
<>
{onToggleAutoAdvance && (
<PlayPauseButton
isPlaying={isAutoAdvancing}
onToggle={onToggleAutoAdvance}
/>
)}
<CloseButton onClick={onClose} />
</>
)}
{...props}
/>;DownloadButton
Download-Button (Icon Download), deaktiviert während isDownloading. Props (DownloadButtonProps): isDownloading, onClick, optional className und style. Die Standard-Controls zeigen ihn, wenn showDownload an ist.
import {
LightboxOverlay,
DownloadButton,
CloseButton,
} from '@reelkit/react-lightbox';
<LightboxOverlay
showDownload
renderControls={({ isDownloading, onDownload, onClose }) => (
<>
{onDownload && (
<DownloadButton isDownloading={isDownloading} onClick={onDownload} />
)}
<CloseButton onClick={onClose} />
</>
)}
{...props}
/>;Hooks
useVideoSlideRenderer
Hook für die optionale Video-Unterstützung. Gibt renderSlide, renderControls und SoundProvider zurück: Umschließe das Overlay mit SoundProvider und übergib die Render-Funktionen. Der Rückgabetyp ist UseVideoSlideRendererResult, der außerdem hasVideo enthält, true, wenn ein Element type: 'video' hat.
Sein renderSlide zeichnet Video-Elemente mit LightboxVideoSlide, einem Video-Slide, der über ein gemeinsames <video>-Element abspielt, damit der Ton auf iOS funktioniert, und sich die Position jedes Videos merkt. Rendere ihn nur aus einem eigenen renderSlide selbst. Seine Props (LightboxVideoSlideProps) sind src, poster, isActive, size, slideKey (Schlüssel für die gespeicherte Position und das aufgenommene Bild), loop (Standard true) und die Callbacks onPlaying, onWaiting, onError, onEnded.
import { useVideoSlideRenderer } from '@reelkit/react-lightbox';
const { renderSlide, renderControls, SoundProvider, hasVideo } =
useVideoSlideRenderer(items);
// SoundProvider — wrap LightboxOverlay in this for mute/unmute support
// renderSlide — pass to LightboxOverlay's renderSlide prop
// renderControls — pass to LightboxOverlay's renderControls prop
// (includes Counter, FullscreenButton, SoundButton, CloseButton)
// hasVideo — true if items contain at least one videouseFullscreen
Hook für den Vollbild-Zustand mit browserübergreifender Unterstützung.
import { useRef } from 'react';
import { useFullscreen } from '@reelkit/react';
function CustomLightbox() {
const containerRef = useRef<HTMLDivElement>(null);
const [isFullscreen, requestFullscreen, exitFullscreen, toggleFullscreen] =
useFullscreen({ ref: containerRef });
return (
<div ref={containerRef}>
<button onClick={toggleFullscreen}>
{isFullscreen.value ? 'Exit Fullscreen' : 'Enter Fullscreen'}
</button>
</div>
);
}Transitions
Übergib eine beliebige TransitionTransformFn über das Prop transitionFn. Importierst du nur die Transition, die du nutzt, kann der Bundler den Rest per Tree-Shaking entfernen. Ohne Angabe gilt slideTransition.
| Funktion | Aus | Beschreibung |
|---|---|---|
slideTransition | @reelkit/react-lightbox | Normaler horizontaler Slide (Standard) |
lightboxFadeTransition | @reelkit/react-lightbox | Überblendung zwischen den Bildern |
flipTransition | @reelkit/react-lightbox | Effekt einer umgedrehten 3D-Karte |
lightboxZoomTransition | @reelkit/react-lightbox | Zoomt von kleiner auf normale Größe |
import {
LightboxOverlay,
lightboxFadeTransition,
} from '@reelkit/react-lightbox';
<LightboxOverlay
isOpen={isOpen}
images={images}
initialIndex={0}
onClose={handleClose}
transitionFn={lightboxFadeTransition}
/>Eigene Transition-Funktion
Schreib deine eigene TransitionTransformFn und übergib sie über transitionFn. Die Signatur entspricht den Transitions des Core-Sliders.
import { LightboxOverlay } from '@reelkit/react-lightbox';
import type { TransitionTransformFn } from '@reelkit/react';
const customFade: TransitionTransformFn = (offset, size) => ({
transform: `translate3d(${offset * size[0]}px, 0, 0)`,
opacity: 1 - Math.min(Math.abs(offset), 1),
});
<LightboxOverlay
isOpen={isOpen}
images={images}
transitionFn={customFade}
onClose={() => setIsOpen(false)}
/>CSS-Klassen
Alle UI-Elemente verwenden einfache CSS-Klassen (keine CSS Modules), die du mit spezifischeren Selektoren in einem Stylesheet ansprechen kannst, das nach @reelkit/react-lightbox/styles.css geladen wird. Für Änderungen an Farbe, Größe und z-index nimm lieber die CSS Custom Properties, die unten im Abschnitt Theming beschrieben sind.
| Klasse | Komponente | Beschreibung |
|---|---|---|
.rk-lightbox-overlay | Overlay | Root-Container (Vollbild-Hintergrund) |
.rk-lightbox-spinner | Overlay | Standard-Spinner beim Laden |
.rk-lightbox-img-error | Overlay | Container für den Fehlerzustand (kaputtes Bild oder Video) |
.rk-lightbox-img-error-text | Overlay | Textlabel für den Fehlerzustand |
.rk-lightbox-swipe-hint | Overlay | Wisch-Hinweis auf Mobilgeräten |
.rk-lightbox-controls-left | Controls | Container der Controls oben links |
.rk-lightbox-btn | Controls | Buttons der Controls (Vollbild und so weiter) |
.rk-lightbox-play-pause | Controls | Play/Pause-Button der Diashow (mit .rk-lightbox-btn) |
.rk-lightbox-download | Controls | Download-Button (mit .rk-lightbox-btn) |
.rk-lightbox-close | Controls | Schließen-Button |
.rk-lightbox-counter | Controls | Chip mit dem Bildzähler |
.rk-lightbox-nav | Navigation | Navigationspfeile (beide) |
.rk-lightbox-nav-prev | Navigation | Pfeil zurück |
.rk-lightbox-nav-next | Navigation | Pfeil weiter |
.rk-lightbox-info | Info | Container für Titel und Beschreibung |
.rk-lightbox-title | Info | Titel des Bildes |
.rk-lightbox-description | Info | Beschreibung des Bildes |
.rk-lightbox-slide | Slide | Container der Slide |
.rk-lightbox-img | Slide | Bild-Element |
.rk-lightbox-video-container | VideoSlide | Container der Video-Slide (optional) |
.rk-lightbox-video-element | VideoSlide | Video-Element (optional) |
.rk-lightbox-video-poster | VideoSlide | Posterbild des Videos (optional) |
Theming
Jede Farbe, Größe, jeder z-index und jede Transition steckt in einer CSS Custom Property. Überschreibe eine oder mehrere auf :root (oder einem beliebigen Vorfahren der Lightbox), um das Aussehen zu ändern, ohne den Quellcode der Komponente anzufassen.
| Token | Standard | Steuert |
|---|---|---|
--rk-lightbox-overlay-bg | #000 | Farbe des Vollbild-Hintergrunds |
--rk-lightbox-overlay-z | 9999 | z-index des Overlays |
--rk-lightbox-top-shade-height | 80px | Höhe des Verlaufs oben |
--rk-lightbox-top-shade-bg | linear-gradient(rgba(0,0,0,0.6), transparent) | Farbe des Verlaufs oben |
--rk-lightbox-edge-padding | 16px | Randabstand für Schließen, Navigation und die Controls oben links |
--rk-lightbox-controls-gap | 12px | Abstand zwischen den Controls oben links |
--rk-lightbox-transition | 0.2s | Dauer der Hover-Transition der Buttons |
--rk-lightbox-blur | 8px | Radius der Hintergrund-Unschärfe für Buttons und Chips |
--rk-lightbox-btn-bg | rgba(0, 0, 0, 0.5) | Standard-Hintergrund für Schließen, Navigation und kleine Buttons |
--rk-lightbox-btn-bg-hover | rgba(255, 255, 255, 0.2) | Hover-Hintergrund für Schließen, Navigation und kleine Buttons |
--rk-lightbox-btn-fg | #fff | Icon-Farbe für Schließen, Navigation und kleine Buttons |
--rk-lightbox-btn-size | 36px | Größe der kleinen Buttons (Vollbild-Schalter und so weiter) |
--rk-lightbox-btn-disabled-opacity | 0.5 | Deckkraft eines deaktivierten kleinen Buttons (laufender Download) |
--rk-lightbox-close-size | 40px | Größe des Schließen-Buttons |
--rk-lightbox-nav-size | 48px | Größe der Pfeile für zurück und weiter |
--rk-lightbox-nav-opacity | 0.7 | Deckkraft der Pfeile im Ruhezustand |
--rk-lightbox-counter-fg | #fff | Textfarbe des Zählers |
--rk-lightbox-counter-bg | rgba(0, 0, 0, 0.5) | Hintergrund des Zähler-Chips |
--rk-lightbox-counter-size | 14px | Schriftgröße des Zählers |
--rk-lightbox-counter-padding | 6px 12px | Innenabstand des Zähler-Chips |
--rk-lightbox-counter-radius | 20px | Eckenradius des Zähler-Chips |
--rk-lightbox-spinner-size | 28px | Breite und Höhe des Standard-Spinners |
--rk-lightbox-spinner-track | rgba(255, 255, 255, 0.2) | Farbe der Spinner-Spur |
--rk-lightbox-spinner-fg | #fff | Farbe der Spinner-Anzeige |
--rk-lightbox-spinner-duration | 0.8s | Dauer einer Spinner-Umdrehung |
--rk-lightbox-error-fg | rgba(255, 255, 255, 0.4) | Farbe von Fehlersymbol und Fehlertext |
--rk-lightbox-error-text-size | 13px | Schriftgröße der Fehlermeldung |
--rk-lightbox-info-bg | linear-gradient(transparent, rgba(0,0,0,0.8)) | Verlauf hinter der Bildunterschrift |
--rk-lightbox-info-padding | 24px | Innenabstand der Bildunterschrift |
--rk-lightbox-title-size | 18px | Schriftgröße des Titels |
--rk-lightbox-description-size | 14px | Schriftgröße der Beschreibung |
--rk-lightbox-info-fg | #fff | Textfarbe der Bildunterschrift |
--rk-lightbox-hint-fg | rgba(255, 255, 255, 0.5) | Textfarbe des Wisch-Hinweises |
--rk-lightbox-hint-bg | rgba(0, 0, 0, 0.3) | Hintergrund des Chips mit dem Wisch-Hinweis |
--rk-lightbox-hint-duration | 3s | Gesamtdauer für das Ein- und Ausblenden des Wisch-Hinweises |
--rk-lightbox-video-bg | #000 | Letterbox-Hintergrund hinter <video> |
Füge das Snippet unten in ein Stylesheet ein, das nach @reelkit/react-lightbox/styles.css geladen wird.
/* Brand the lightbox */
:root {
--rk-lightbox-overlay-bg: #0f172a;
--rk-lightbox-btn-bg: rgba(99, 102, 241, 0.65);
--rk-lightbox-btn-bg-hover: rgba(168, 85, 247, 0.85);
--rk-lightbox-nav-size: 56px;
--rk-lightbox-counter-bg: rgba(99, 102, 241, 0.65);
--rk-lightbox-info-bg: linear-gradient(
transparent,
rgba(99, 102, 241, 0.55) 60%,
rgba(168, 85, 247, 0.85)
);
}Barrierefreiheit
Die Wurzel des Overlays ist ein modaler Dialog (role="dialog", aria-modal="true"). Mit ariaLabel änderst du, was der Screenreader ansagt; standardmäßig ist es "Image gallery". Jede Slide trägt role="group", aria-roledescription="slide" und aria-label="Image N of M".
Die Lightbox holt beim Öffnen den Fokus und gibt ihn beim Schließen an den Auslöser zurück. Tab und Umschalt+Tab wechseln zwischen den fokussierbaren Elementen darin; Fokus, der entwischt (ein Klick daneben, ein Fokus per Code), wird zurückgeholt. Umgesetzt mit captureFocusForReturn und createFocusTrap aus @reelkit/core.
Tastenkürzel
| Taste | Aktion |
|---|---|
ArrowLeft | Vorheriges Bild |
ArrowRight | Nächstes Bild |
Escape | Schließt die Lightbox (oder beendet das Vollbild, falls aktiv) |