Vue-Leitfaden

So baust du Slider mit @reelkit/vue.

Touch zuerst
Wischen mit Schwung und Einrasten
Tastatur
Pfeiltasten + Escape
Mausrad
Optional, mit Debounce
Virtualisiert
Über 10.000 Elemente, 3 im DOM
Indikatoren
Mitlaufende Punkte wie bei Instagram
API im Code
next(), prev(), goTo() über ein Template-Ref
Loop-Modus
Endlose Navigation im Kreis
Richtung
Vertikal oder horizontal
Composition API
<script setup> mit Composables

Einfacher Slider

Die Komponente <Reel> umschließt den Slider-Controller aus dem Core. Mit dem Slot #item renderst du jeden Slide virtualisiert — nur die sichtbaren Slides werden gemountet.

App.vue
<script setup lang="ts">
import { Reel, ReelIndicator } from '@reelkit/vue';

const items = [
  { title: 'Virtualized', subtitle: 'Only 3 slides in DOM', color: '#6366f1' },
  { title: 'Touch First', subtitle: 'Native swipe gestures', color: '#8b5cf6' },
  { title: 'Zero Deps', subtitle: 'Tiny bundle size', color: '#7c3aed' },
  { title: 'Keyboard Nav', subtitle: 'Full a11y support', color: '#ec4899' },
  { title: 'SSR Ready', subtitle: 'Works everywhere', color: '#14b8a6' },
  { title: '60fps', subtitle: 'Smooth animations', color: '#f59e0b' },
];

const onAfterChange = (index: number) => {
  console.log('Current index:', index);
};
</script>

<template>
  <Reel
    :count="items.length"
    style="width: 100%; height: 100dvh"
    direction="vertical"
    :enable-wheel="true"
    @after-change="onAfterChange"
  >
    <template #item="{ index, size }">
      <div
        :style="{
          width: size[0] + 'px',
          height: size[1] + 'px',
          background: items[index].color,
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'center',
          color: '#fff',
        }"
      >
        <div style="font-size: 1.5rem; font-weight: bold">
          {{ items[index].title }}
        </div>
        <div style="font-size: 0.875rem; opacity: 0.8">
          {{ items[index].subtitle }}
        </div>
      </div>
    </template>

    <div style="position: absolute; right: 12px; top: 50%; transform: translateY(-50%); z-index: 10">
      <ReelIndicator direction="vertical" />
    </div>
  </Reel>
</template>

ReelIndicator

Eine optionale Komponente, die Fortschrittsindikatoren wie bei Instagram zeigt und so die aktuelle Position im Slider anzeigt. Steht sie innerhalb eines <Reel>, verbindet sie sich über provide/inject von Vue selbst mit count und active der Elternkomponente — ohne Verdrahtung von Hand.

vue-html
<!-- Auto-connect: count and active are inherited from parent Reel -->
<Reel :count="10" :size="[400, 600]">
  <template #item="{ index, size }"> ... </template>
  <ReelIndicator direction="vertical" />
</Reel>

<!-- Manual usage: pass count and active explicitly (e.g. outside a Reel) -->
<ReelIndicator :count="10" :active="currentIndex" />

Imperative API — Template-Ref

Die Komponente <Reel> stellt über ein Template-Ref die Schnittstelle ReelExpose bereit. Speichere die Referenz mit ref() und rufe imperative Methoden wie next(), prev() und goTo() auf.

vue
<script setup lang="ts">
import { ref } from 'vue';
import { Reel, type ReelExpose } from '@reelkit/vue';

const items = [
  { title: 'Slide 1', color: '#6366f1' },
  { title: 'Slide 2', color: '#8b5cf6' },
  { title: 'Slide 3', color: '#ec4899' },
];

const sliderRef = ref<ReelExpose | null>(null);
const currentIndex = ref(0);

const onAfterChange = (index: number) => {
  currentIndex.value = index;
};
</script>

<template>
  <Reel
    ref="sliderRef"
    :count="items.length"
    style="width: 100%; height: 100dvh"
    direction="vertical"
    :enable-wheel="true"
    @after-change="onAfterChange"
  >
    <template #item="{ index, size }">
      <div
        :style="{
          width: size[0] + 'px',
          height: size[1] + 'px',
        }"
      >
        {{ items[index].title }}
      </div>
    </template>
  </Reel>

  <div style="position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%)">
    <button
      :disabled="currentIndex === 0"
      @click="sliderRef?.prev()"
    >
      Prev
    </button>
    <button
      :disabled="currentIndex === items.length - 1"
      @click="sliderRef?.next()"
    >
      Next
    </button>
    <button @click="sliderRef?.goTo(2)">Go to 3</button>
  </div>
</template>

Horizontale Richtung

Setze direction="horizontal" für einen Slider, der nach links und rechts gewischt wird. Die Richtung des Indikators sollte dazu passen.

vue-html
<Reel
  :count="items.length"
  :size="[400, 300]"
  direction="horizontal"
>
  <template #item="{ index, size }">
    <div :style="{ width: size[0] + 'px', height: size[1] + 'px' }">
      {{ items[index].title }}
    </div>
  </template>

  <ReelIndicator direction="horizontal" />
</Reel>

Automatische Größe

Das Prop size ist optional. Fehlt es, misst <Reel> seinen Container per ResizeObserver selbst und passt sich einem per CSS bestimmten Layout an. Die Größe des Containers muss von der Elternkomponente kommen (zum Beispiel Flex, Grid oder feste CSS-Maße).

vue-html
<!-- Explicit size (fixed) -->
<Reel :count="items.length" :size="[400, 600]">
  <template #item="{ index, size }"> ... </template>
</Reel>

<!-- Auto-size (responsive — sized by CSS) -->
<Reel :count="items.length" style="width: 100%; height: 100dvh">
  <template #item="{ index, size }"> ... </template>
</Reel>

Übergänge

Übergib ein Prop transition, um die Slide-Animation anzupassen. ReelKit liefert fünf Übergänge, die sich per Tree-Shaking entfernen lassen: slideTransition (Standard), fadeTransition, flipTransition, cubeTransition und zoomTransition.

vue
<script setup lang="ts">
import { Reel, cubeTransition } from '@reelkit/vue';
</script>

<template>
  <Reel
    :count="items.length"
    :size="[400, 600]"
    :transition="cubeTransition"
  >
    <template #item="{ index, size }"> ... </template>
  </Reel>
</template>

Loop-Modus

Mit dem Prop loop schaltest du die endlose Navigation im Kreis ein. Der Slider springt nahtlos vom letzten Slide zurück zum ersten (und umgekehrt).

vue-html
<Reel
  :count="items.length"
  :size="[400, 600]"
  :loop="true"
>
  <template #item="{ index, size }"> ... </template>
</Reel>

Event-Callbacks

Die Komponente <Reel> sendet mehrere Events, mit denen du den Zustand des Sliders verfolgst:

vue
<script setup lang="ts">
import { Reel } from '@reelkit/vue';

const onBeforeChange = (index: number, nextIndex: number, rangeIndex: number) => {
  console.log('Transitioning from', index, 'to', nextIndex);
};

const onAfterChange = (index: number, rangeIndex: number) => {
  console.log('Arrived at slide', index);
};

const onSlideDragStart = (index: number) => {
  console.log('Started dragging slide', index);
};

const onSlideDragEnd = (index: number) => {
  console.log('Stopped dragging slide', index);
};
</script>

<template>
  <Reel
    :count="20"
    style="width: 100%; height: 100dvh"
    @before-change="onBeforeChange"
    @after-change="onAfterChange"
    @slide-drag-start="onSlideDragStart"
    @slide-drag-end="onSlideDragEnd"
  >
    <template #item="{ index, size }"> ... </template>
  </Reel>
</template>

Eingebaute Wege der Navigation:

  • Touch/Wischen: Ziehen navigiert mit Schwung und Einrasten
  • Tastatur: Pfeiltasten und Escape
  • Mausrad: Einschalten mit :enable-wheel="true"
  • Im Code: Über ein Template-Ref erreichst du next(), prev(), goTo()
vue
<script setup lang="ts">
import { ref } from 'vue';
import { Reel, type ReelExpose } from '@reelkit/vue';

const sliderRef = ref<ReelExpose | null>(null);
</script>

<template>
  <Reel
    ref="sliderRef"
    :count="10"
    :size="[400, 600]"
  >
    <template #item="{ index, size }">
      <div :style="{ width: size[0] + 'px', height: size[1] + 'px' }">
        Slide {{ index + 1 }}
      </div>
    </template>
  </Reel>

  <button @click="sliderRef?.prev()">Prev</button>
  <button @click="sliderRef?.next()">Next</button>
  <button @click="sliderRef?.goTo(5)">Go to 6</button>
</template>

URL-State

useOverlayUrlState baut einen URL-State-Controller für ein Overlay und gibt ihn vollständig zurück; den übergibst du einem <LightboxUrlOverlay> als Prop :controller. Die Adressleiste bestimmt, ob es offen ist, also öffnet sich ein gebundenes Overlay selbst, und ein Link ist die übliche Aktion zum Öffnen. Der erste Schreibvorgang eines fehlenden Parameters legt einen Eintrag im Verlauf an, jeder weitere ersetzt ihn, sodass das Blättern den Zurück-Button nie verschüttet. Behalte den Controller, um value/position zu lesen und per set(null) im Code zu schließen — derselbe einfache Schreibvorgang, den das Overlay intern beim Wechsel des Slides nutzt.

vue
<script setup lang="ts">
import { LightboxUrlOverlay, type LightboxItem } from '@reelkit/vue-lightbox';
import { useOverlayUrlState, urlIndexKey } from '@reelkit/vue';

const props = defineProps<{ images: LightboxItem[] }>();

const photo = useOverlayUrlState({
  param: 'photo',
  ...urlIndexKey(() => props.images.length),
});
</script>

<template>
  <!-- Opening is a link — the overlay reads the URL and opens itself. -->
  <RouterLink v-for="(img, i) in props.images" :key="img.src" :to="`?photo=${i}`">
    <img :src="img.src" />
  </RouterLink>

  <LightboxUrlOverlay :controller="photo" :items="props.images" />
</template>

Das Optionsobjekt nimmt param, codec und locator (alle drei Pflicht) sowie einen optionalen adapter. codec und locator sind ein zusammengehöriges Paar mit demselben Id, also verteilst du für eine einfache Galerie mit ?photo=3 einfach ...urlIndexKey(() => props.images.length), das beide Hälften auf einmal liefert. urlIndexKey bildet den Parameter auf einen Slide-Index ab und prüft ihn gegen die aktuelle 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: Ein setup in Vue läuft nur einmal, eine dort festgehaltene Länge veraltet also, sobald ein seitenweise geladener Feed wächst. Es umschließt createIndexLocator (die Locator-Hälfte) und kombiniert es mit indexCodec. Ein seitenweise geladener Feed oder eine Galerie mit Schlüsseln nach Identität liefert stattdessen ein eigenes Paar aus codec + locator. Die vollständige Tabelle der Optionen steht in der Vue-API-Referenz.

Muster mit dem Slot #item

Statt eines Render-Props wie in React nutzt Vue den Scoped Slot #item. Das macht die Virtualisierung möglich — nur sichtbare Slides werden gemountet. Der Scope des Slots liefert drei Eigenschaften:

vue-html
<template #item="{ index, indexInRange, size }">
  <!--
    index        : number          — absolute slide index (0 to count-1)
    indexInRange  : number          — position in visible window (0, 1, or 2)
    size          : [number, number] — [width, height] of the container
  -->
  <MySlide
    :data="items[index]"
    :style="{ width: size[0] + 'px', height: size[1] + 'px' }"
  />
</template>

Composables

@reelkit/vue bringt Composables für die häufigen Overlay-Fälle mit:

vue
<script setup lang="ts">
import { ref } from 'vue';
import { useBodyLock, useFullscreen, useReelContext } from '@reelkit/vue';

// Lock body scroll when an overlay is open
const isOpen = ref(true);
useBodyLock(isOpen);

// Fullscreen API with cross-browser support
const containerRef = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen({ elementRef: containerRef });

// Access parent Reel context (when inside a Reel)
const reelContext = useReelContext();
// reelContext?.index  — active slide index signal
// reelContext?.count  — total slide count signal
// reelContext?.goTo() — navigate programmatically

// Bridge a core Subscribable into a reactive Vue ref
import { toVueRef } from '@reelkit/vue';
const index = toVueRef(reelContext!.index); // Ref<number> — re-renders on change
</script>

Das Wichtigste

  • Composition API

    Importiere Reel, ReelIndicator und die Composables direkt in dein <script setup> — eine Plugin-Registrierung ist nicht nötig

  • Scoped Slot #item

    Das Vue-Gegenstück zum Prop itemBuilder aus React — Virtualisierung mit vertrauter Template-Syntax

  • Template-Ref

    Nutze ref<ReelExpose>() für die imperative Navigation — Event-Callbacks sind dafür nicht nötig

  • @after-change

    Sendet (index, rangeIndex) — verfolge den aktuellen Index für Updates der Oberfläche

  • Kontext über provide/inject

    ReelIndicator verbindet sich über provide/inject von Vue selbst mit dem umgebenden Reel — kein Durchreichen von Props von Hand

Tipps zur Performance

  • Halte die Slide-Templates schlank

    Der Slot #item läuft für jeden sichtbaren Slide (meist 3 gleichzeitig). Vermeide darin aufwendige Berechnungen oder tief verschachtelte Strukturen.

  • Lade Daten kurz vor dem Ende

    Erkenne mit @after-change, wann sich der Nutzer dem Ende nähert, und lade den nächsten Schwung, bevor die Slides ausgehen — so entstehen endlos scrollende Feeds.

  • Nutze Refs für den imperativen Zustand

    Lege die Referenz auf ReelExpose und den aktuellen Index in ref()s von Vue ab, für feingranulare Reaktivität ohne unnötige Re-Renders.

  • Schalte das Mausrad auf scrollbaren Seiten ab

    Setze :enable-wheel="false", wenn der Slider in einem scrollbaren Layout steckt, damit er das Scrollen der Seite nicht abfängt.

Nächste Schritte