Vue-Leitfaden
So baust du Slider mit @reelkit/vue.
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.
<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.
<!-- 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.
<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.
<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).
<!-- 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.
<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).
<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:
<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>Navigation
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()
<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.
<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:
<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:
<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,ReelIndicatorund die Composables direkt in dein<script setup>— eine Plugin-Registrierung ist nicht nötig - Scoped Slot #item
Das Vue-Gegenstück zum Prop
itemBuilderaus 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
ReelIndicatorverbindet sich über provide/inject von Vue selbst mit dem umgebendenReel— kein Durchreichen von Props von Hand
Tipps zur Performance
- Halte die Slide-Templates schlank
Der Slot
#itemlä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
ReelExposeund den aktuellen Index inref()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
- Vue-API-Referenz - alle Props, Events und Composables
- Core-Leitfaden - die Engine ohne Framework
- Reel Player - Videoplayer im Stil von TikTok/ReelsReel Player - Videoplayer im Stil von TikTok/ReelsReel Player - Videoplayer im Stil von TikTok/Reels
- Lightbox - Galerie für Bilder und VideosLightbox - Galerie für Bilder und VideosLightbox - Galerie für Bilder und Videos