React-Leitfaden

So baust du Slider mit @reelkit/react.

Touch zuerst
Wischen mit Schwung und Einrasten
Tastatur
Pfeiltasten + Escape
Mausrad
Optional, mit Debounce
Virtualisiert
10.000+ Einträge, 3 im DOM
Indikatoren
Punkte, die wie bei Instagram mitlaufen
API im Code
next(), prev(), goTo() über ref
Loop-Modus
Endlose, kreisförmige Navigation
Richtung
Vertikal oder horizontal
Keine Re-Renders
Zustandsupdates über Signals

Reel-Komponente

Die Komponente Reel ist der Hauptcontainer, der den Zustand des Sliders verwaltet und sich um Touch-Gesten, Tastaturnavigation und Animationen kümmert.

Im DOM liegen immer nur drei Slides, der aktuelle und seine Nachbarn, daher kostet eine Liste mit zehntausend Einträgen so viel wie eine mit zehn. Wie das in einem ganzen Feature aussieht, etwa in einem endlosen Feed oder einem Raster, das einen Player öffnet, zeigen die Rezepte.

tsx
import { Reel, ReelIndicator } from '@reelkit/react';

<Reel
  count={items.length}
  size={[width, height]}
  direction="vertical"
  enableWheel
  afterChange={(index) => console.log('Current:', index)}
  itemBuilder={(index, indexInRange, size) => (
    <div style={{ width: size[0], height: size[1] }}>
      Slide {index}
    </div>
  )}
>
  {/* Optional children like ReelIndicator */}
</Reel>

Automatische Größe

Das Prop size ist optional. Lässt du es weg, misst Reel seinen Container per ResizeObserver selbst aus und passt sich dem Layout aus dem CSS an. Die Größe des Containers muss dabei vom Elternelement kommen (zum Beispiel Flex, Grid oder feste Maße im CSS).

tsx
// Explicit size (fixed)
<Reel count={items.length} size={[400, 600]} itemBuilder={...} />

// Auto-size (responsive — sized by CSS)
<Reel count={items.length} style={{ width: '100%', height: '100dvh' }} itemBuilder={...} />

Das itemBuilder-Muster

Das Prop itemBuilder ist eine Funktion, die den Index bekommt und den Inhalt des jeweiligen Slides zurückgibt. Dieses Muster macht die Virtualisierung möglich: Gerendert werden nur die sichtbaren Einträge.

tsx
itemBuilder={(index, indexInRange, size) => {
  // index: actual item index (0 to count-1)
  // indexInRange: position in visible window (0, 1, or 2)
  // size: [width, height] of the container
  return <Slide index={index} />;
}}

Eingebaute Wege der Navigation:

  • Touch/Wischen: Ziehen mit Schwung und Einrasten
  • Tastatur: Pfeiltasten und Escape
  • Mausrad: Aktivierst du mit dem Prop enableWheel
  • Im Code: Über apiRef mit next(), prev(), goTo()
tsx
import { useRef } from 'react';
import { Reel, type ReelApi } from '@reelkit/react';

function App() {
  const apiRef = useRef<ReelApi>(null);

  return (
    <>
      <Reel
        count={10}
        size={[400, 600]}
        apiRef={apiRef}
        itemBuilder={(index) => <Slide index={index} />}
      />
      <button onClick={() => apiRef.current?.prev()}>Prev</button>
      <button onClick={() => apiRef.current?.next()}>Next</button>
      <button onClick={() => apiRef.current?.goTo(5)}>Go to 5</button>
    </>
  );
}

URL-State

useOverlayUrlState baut einen URL-State-Controller für ein Overlay und gibt ihn vollständig zurück. Den übergibst du dann einem *UrlOverlay als Prop controller. Die URL bestimmt, ob es offen ist, ein gebundenes Overlay öffnet sich also selbst, und ein Link ist der übliche Weg, es zu öffnen. Das erste Schreiben eines noch fehlenden Parameters legt einen Eintrag im Verlauf an, jedes weitere ersetzt ihn, so begräbt das Blättern nie den Zurück-Button. Behalte den Controller, um value/position zu lesen und ihn im Code zu steuern: set(position) öffnet, set(null) schließt, und set ist derselbe einfache Schreibzugriff, den das Overlay intern beim Slide-Wechsel nutzt.

tsx
import { useOverlayUrlState, urlIndexKey } from '@reelkit/react';
import { useReactRouterUrlAdapter } from '@reelkit/react/react-router-url-adapter';
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 overlay reads the URL and opens itself.
<Link to="?photo=3"><img src={images[3].src} /></Link>
<LightboxUrlOverlay controller={photo} images={images} />

// Read the url-derived state, or close programmatically (a low-level write).
photo.position.value; // 3 for ?photo=3, null when nothing is open
photo.set(null); // close

// Routed app: pass a router-backed adapter, otherwise the router's
// own location goes stale and its next navigation drops the param.
const adapter = useReactRouterUrlAdapter();
const routed = useOverlayUrlState({
  param: 'photo',
  adapter,
  ...urlIndexKey(() => images.length),
});

Das Optionsobjekt nimmt param, codec und locator (alle drei Pflicht) sowie einen optionalen adapter. param und adapter werden beim ersten Render gelesen und bleiben danach fest – zum Ändern musst du neu mounten –, während codec und locator live gelesen werden, sodass eine Suche nach dem Wachsen oder Umsortieren der Liste die aktuelle Liste sieht. codec und locator sind ein zusammengehöriges Paar mit demselben Id und reisen deshalb gemeinsam – für eine einfache Galerie mit ?photo=3 spreizt du ...urlIndexKey(() => images.length), das beide Hälften auf einmal zurückgibt. urlIndexKey bildet den Parameter auf einen Slide-Index ab und begrenzt ihn mit der aktuellen Anzahl, die der Getter liefert. Ein veraltetes oder zu großes ?photo=99 wird so abgelehnt und verschwindet von selbst aus der URL, statt einen Slide zu öffnen, den nie jemand gemeint hat. Übergib einen Getter, keine Zahl, damit die Grenze stimmt, während ein Feed mit Paginierung wächst. Es kapselt createIndexLocator (die Locator-Hälfte) und kombiniert es mit indexCodec. Ein Feed mit Paginierung oder eine Galerie mit Schlüsseln nach Identität bringt stattdessen ein eigenes passendes Paar aus codec + locator mit. Die vollständige Tabelle der Optionen steht in der React-API-Referenz.

ReelIndicator

Optionale Komponente, die Fortschrittsindikatoren wie bei Instagram zeigt und damit die aktuelle Position im Slider. Liegt sie in einem Reel, verbindet sie sich über den Context selbst mit den Werten count und active des Elternelements – ganz ohne manuelles Verdrahten von Zustand.

tsx
import { Reel, ReelIndicator } from '@reelkit/react';

{/* Auto-connect: count and active are inherited from parent Reel */}
<Reel count={10} size={[400, 600]} itemBuilder={...}>
  <ReelIndicator />
</Reel>

{/* Manual usage: pass count and active explicitly (e.g. outside a Reel) */}
<ReelIndicator count={10} active={currentIndex} />

Das Wichtigste

  • Prop size

    Optionales Tupel [width, height]; lass es weg, wenn die Größe aus dem CSS kommen soll

  • itemBuilder

    Bekommt den Index und gibt den Inhalt des Slides zurück

  • apiRef

    Zugriff auf die Methoden des Controllers für die Navigation

  • afterChange

    Verfolgt den aktuellen Index für Updates der UI

Tipps für die Performance

  • Datenarrays memoisieren

    Umschließe dein Array mit Einträgen mit useMemo. Eine neue Array-Referenz bei jedem Render löst ein Update von count aus und berechnet die sichtbaren Bereiche neu.

  • itemBuilder schlank halten

    Er läuft bei jeder Änderung des sichtbaren Bereichs (meist 3 Slides). Vermeide darin aufwendige Berechnungen oder Seiteneffekte.

  • Daten kurz vor dem Ende laden

    Erkenne mit afterChange, wann sich der Nutzer dem Ende nähert, und lade den nächsten Schwung, bevor ihm die Slides ausgehen (siehe das Rezept „Endloser Feed“).

  • Mausrad auf scrollbaren Seiten abschalten

    Setze enableWheel={false}, wenn der Slider in einem scrollbaren Layout steckt, damit er das Scrollen der Seite nicht abfängt.

Nächste Schritte