Guia para Vue

Aprenda a montar sliders com @reelkit/vue.

Toque em primeiro lugar
Deslize com inércia e encaixe
Navegação por teclado
Setas e Escape
Roda do mouse
Opcional, com debounce
Virtualizado
Mais de 10.000 itens, 3 no DOM
Indicadores
Pontinhos deslizantes no estilo Instagram
API programática
next(), prev(), goTo() por ref de template
Modo cíclico
Navegação circular sem fim
Nos dois sentidos
Vertical ou horizontal
Composition API
<script setup> com composables

Slider básico

O componente <Reel> embrulha o controlador do slider do core. Use o slot #item para desenhar cada slide com virtualização — só os slides visíveis são montados.

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

Componente opcional que mostra indicadores de progresso no estilo Instagram, sinalizando a posição atual dentro do slider. Colocado dentro de um <Reel>, ele se liga sozinho aos valores de count e active do componente pai pelo provide/inject do Vue — sem ligação manual.

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" />

API imperativa — ref de template

O componente <Reel> expõe a interface ReelExpose por um ref de template. Use ref() para guardar a referência e chamar métodos imperativos como next(), prev() e goTo().

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>

Direção horizontal

Defina direction="horizontal" para um slider que desliza para os lados. A direção do indicador deve acompanhar.

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>

Dimensionamento automático

A prop size é opcional. Sem ela, o <Reel> mede o próprio contêiner com ResizeObserver e acompanha o layout definido no CSS. O contêiner precisa ganhar tamanho do elemento pai (flex, grid ou dimensões explícitas no CSS).

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>

Transições

Passe a prop transition para personalizar a animação entre os slides. O ReelKit traz cinco transições que saem no tree-shaking: slideTransition (padrão), fadeTransition, flipTransition, cubeTransition e 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>

Modo cíclico

Ligue a navegação circular sem fim com a prop loop. O slider passa do último slide ao primeiro sem emenda (e vice-versa).

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

Callbacks de eventos

O componente <Reel> emite vários eventos para você acompanhar o estado do slider:

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>

Formas de navegação já incluídas:

  • Toque/Deslizar: arraste para navegar, com inércia e encaixe
  • Teclado: setas e Escape
  • Roda do mouse: ligue com :enable-wheel="true"
  • Programática: use um ref de template para alcançar 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>

Estado na URL

useOverlayUrlState monta um controlador de estado na URL para o overlay e devolve o controlador inteiro; você o entrega a um <LightboxUrlOverlay> pela prop :controller. Quem manda no estado aberto é a barra de endereços, então um overlay ligado a ela se abre sozinho e o link vira a forma normal de abrir. A primeira escrita de um parâmetro ausente empilha uma entrada no histórico e cada escrita seguinte a substitui, então percorrer os itens nunca soterra o botão voltar. Guarde o controlador para ler value/position e para fechar por código com set(null), a mesma escrita de baixo nível que o overlay usa internamente ao trocar de slide.

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>

O objeto de opções recebe param, codec e locator (os três obrigatórios), mais um adapter opcional. O codec e o locator formam um par combinado que divide o mesmo Id, então em uma galeria simples com ?photo=3 espalhe ...urlIndexKey(() => props.images.length), que devolve as duas metades de uma vez. urlIndexKey liga o parâmetro a um índice de slide e o limita pela contagem atual que o getter devolve, então um ?photo=99 velho ou fora do intervalo é rejeitado e se cura sozinho saindo da URL, em vez de abrir um slide que ninguém nomeou. Passe um getter, e não um número: o setup do Vue roda uma única vez, então um comprimento capturado envelheceria conforme o feed paginado cresce. Por baixo, ele embrulha createIndexLocator (a metade do locator) e a combina com indexCodec. Um feed paginado ou uma galeria endereçada por identidade fornece o próprio par codec + locator. A tabela completa de opções está na referência da API para Vue.

Padrão do slot #item

No lugar da render prop do React, o Vue usa o slot com escopo #item. É ele que viabiliza a virtualização — só os slides visíveis são montados. O escopo do slot entrega três propriedades:

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 traz composables para os cenários comuns de overlay:

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>

Pontos principais

  • Composition API

    Importe Reel, ReelIndicator e os composables direto no seu <script setup> — não é preciso registrar plugin nenhum

  • slot com escopo #item

    O equivalente, no Vue, à prop itemBuilder do React — dá virtualização com a sintaxe de template de sempre

  • Ref de template

    Use ref<ReelExpose>() para navegar de forma imperativa — sem precisar de callbacks de evento

  • @after-change

    Emite (index, rangeIndex) — acompanhe o índice atual para atualizar a interface

  • contexto por provide/inject

    O ReelIndicator se liga sozinho ao Reel que o envolve pelo provide/inject do Vue — sem passar props de mão em mão

Dicas de desempenho

  • Deixe os templates dos slides leves

    O slot #item roda para cada slide visível (normalmente 3 por vez). Evite cálculos pesados ou estruturas muito aninhadas lá dentro.

  • Carregue os dados perto da borda

    Use @after-change para perceber quando o leitor se aproxima do fim e busque o lote seguinte antes que os slides acabem — é assim que nasce um feed sem fim.

  • Use refs para o estado imperativo

    Guarde a referência de ReelExpose e o índice atual em ref()s do Vue, para ter reatividade fina sem renderizações desnecessárias.

  • Desligue a roda em páginas roláveis

    Defina :enable-wheel="false" quando o slider estiver dentro de um layout rolável, para não capturar a rolagem da página.

Próximos passos