Vue Reel Player

Player vertical de mídia em tela cheia no estilo Instagram/TikTok para Vue 3, construído sobre @reelkit/vue-reel-player.

Ver demonstração ao vivo →

Recursos

Deslize vertical
Toque, arrasto, teclado, roda
Vídeo automático
Toca quando fica visível
Botão de som
Continuidade no iOS
Várias mídias
Carrosséis horizontais aninhados
Memória de posição
Retoma de onde parou
Captura de quadro
Transição suave do pôster ao vídeo
Virtualizado
Apenas 3 slides no DOM
Proporção
9:16 no desktop, tela inteira no celular
Navegação no desktop
Botões de seta
Tipos genéricos
Modelos de dados próprios
Slots com escopo
Personalize cada elemento da interface
v-model:is-open
Ligação de mão dupla na visibilidade
Estado na URL
Links para compartilhar, fechamento no botão voltar

Instalação

bash

Importe a folha de estilo uma vez na entrada do seu app (ou em qualquer componente):

typescript
Ícones

Os controles padrão usam lucide-vue-next para os ícones (fechar, som, setas de navegação). Se você prefere outra biblioteca de ícones, use os slots com escopo #controls e #navigation para fornecer os seus.

Uso básico

Desenhe uma grade de miniaturas e abra o overlay no índice clicado. Ligar v-model:is-open mantém o ref do componente pai em sintonia quando o leitor fecha o player pelo botão, por gesto ou com Escape.

App.vue

Slots com escopo

Oito slots com escopo permitem trocar qualquer parte da interface do player. Cada um recebe um objeto de escopo fortemente tipado. Os slots que você não passar caem no padrão.

SlotEscopoDescrição
#controls{ item, soundState, activeIndex, content, onClose }Barra de controles global própria (fechar, som, compartilhar e afins)
#error{ item, activeIndex, innerActiveIndex }Indicador de erro próprio (substitui o ícone padrão)
#loading{ item, activeIndex, innerActiveIndex }Indicador de carregamento próprio (substitui a animação padrão de onda)
#navigation{ item, activeIndex, count, onPrev, onNext }Setas próprias de avançar e voltar (desktop)
#nestedNavigation{ media, activeIndex, count, onPrev, onNext }Setas próprias do slider horizontal interno
#nestedSlide{ item, media, index, size, isActive, isInnerActive, slideKey, defaultContent, onReady, onWaiting, onError }Conteúdo próprio dos slides dentro do slider horizontal interno
#slide{ item, index, size, isActive, slideKey, defaultContent, onReady, onWaiting, onError }Conteúdo de slide totalmente seu (sem ele, vale o padrão)
#slideOverlay{ item, index, isActive }Camada de cada slide (dados do autor, curtidas, descrição e afins)
#timeline{ item, activeIndex, timelineState, defaultContent }Barra de reprodução própria. Só é chamada quando a regra embutida (modo da linha do tempo e duração mínima) renderizaria a barra padrão; reaproveita a mesma lógica de auto/always/never. Use defaultContent() para envolver a <TimelineBar /> embutida.
vue

Linha do tempo própria

Troque a barra de reprodução padrão pela sua, com o slot #timeline. O slot só dispara quando as regras do overlay renderizariam a barra padrão (o mesmo modo timeline e o mesmo timelineMinDurationSeconds), então você não precisa reimplementá-las. Reaproveite a classe .rk-reel-timeline na sua raiz para herdar o posicionamento rente à base, o respiro da área segura e o espaço reservado em dispositivos de toque.

vue

Tipos de conteúdo próprios

ReelPlayerOverlay é genérico em relação ao formato dos seus itens de conteúdo. Estenda BaseContentItem para usar qualquer modelo de dados e importe o tipo de escopo de slot correspondente para manter as ligações fortemente tipadas:

vue

O mesmo padrão vale para todos os outros slots. Importe o tipo de escopo correspondente (SlideSlotScope, ControlsSlotScope, NavigationSlotScope, NestedSlideSlotScope, LoadingSlotScope) e anote a desestruturação.

Estado na URL

Ver demonstração ao vivo →

Monte um controlador com useOverlayUrlState, de @reelkit/vue, e entregue-o ao ReelPlayerUrlOverlay como controller: quem manda no player é a barra de endereços, então ele abre quando o parâmetro nomeia um slide e fecha quando o parâmetro some. Abrir empilha uma entrada no histórico e cada troca de slide a substitui, então percorrer um feed não acrescenta entradas e um único passo para trás sempre sai. A profundidade da URL acompanha a chave do controlador: um urlIndexKey de eixo único endereça apenas o post (?reel=3); um urlIndexTwoAxisKey de dois eixos carrega também o índice da mídia interna de um post com várias mídias (?reel=3.2); escolha uma chave por app, porque os dois formatos não são decodificados um pelo outro. É um componente separado do ReelPlayerOverlay, então cada um carrega exatamente um comando do estado aberto — o modelo is-open ou o controller da URL, nunca os dois.

Chaves prontas

Você pode endereçar os slides com uma chave pronta — espalhe urlIndexKey (por posição) ou urlStableIdKey (por um id estável) no controlador — as duas reexportadas por @reelkit/vue. Veja o guia de estado na URL e a API do core.

Um app com rotas deve passar um adaptador ligado ao roteador, para que ele siga sendo a única fonte de verdade da navegação — escrever no histórico por trás dele deixa sua localização desatualizada e derruba o parâmetro na navegação seguinte. useVueRouterUrlAdapter, de @reelkit/vue/vue-router-url-adapter, é o adaptador pronto para o Vue Router.

vue

As opções completas de useOverlayUrlState estão na referência da API para Vue.

  • Abrir empilha uma entrada no histórico. Percorrer o feed substitui essa entrada, então N deslizes não acrescentam nenhuma e um único passo para trás sempre sai do player. Voltar fecha; não troca de slide.
  • Voltar só fecha quando o player foi aberto de dentro do app — foi o link que empilhou a entrada. Um link compartilhado aberto direto em uma aba nova não tem histórico atrás de si, então o voltar do navegador sai do site; o botão ✕ ou o Escape removem o parâmetro ali mesmo e você continua na página.
  • Um link direto ?reel=3 abre o player naquele slide já no carregamento.
  • Um parâmetro que não nomeia slide nenhum — um favorito velho, um valor editado à mão — é retirado da URL, em vez de deixar a barra de endereços afirmando um slide que não abre.
  • A profundidade da URL acompanha a chave do controlador: eixo único para o post apenas, ou dois eixos (urlIndexTwoAxisKey) para carregar também o índice da imagem interna de um post com várias mídias. Escolha uma chave por app; os formatos não são decodificados um pelo outro.

Uma chave ou duas — escolha a profundidade da URL

O mesmo ReelPlayerOverlay conduz os dois formatos; ele distingue em tempo de execução pela posição do controlador, então não existe prop de modo. Escolha a chave ao montar o controlador:

ChaveFioO que carrega
urlIndexKey(…)?reel=3Apenas o post vertical.
urlIndexTwoAxisKey(…)?reel=3.2O post e o índice da mídia interna do carrossel.

Os dois fios são deliberadamente distintos — uma chave de dois eixos é estritamente pontuada (3.0, nunca um 3 solto), então um link de um eixo não é decodificado pelo outro. Trocar a chave de um app, portanto, invalida qualquer link já compartilhado. Escolha um formato e fique com ele.

typescript

Links estáveis. O índice é posicional, então um ?reel=3 favoritado abre outro post assim que o feed muda de ordem — e num feed isso é o normal, não a exceção. urlStableIdKey usa o id estável de cada post, varrendo o feed atual — uma chamada resolve o caso comum.

typescript

Passe hashCodec: base64UrlCodec para escrever o id em base64url na URL — é um disfarce reversível, não um hash criptográfico.

Quer endereçar por outro campo (um slug), ou paginar um feed infinito com locateAsync? Monte o codec e o locator você mesmo. São dois papéis distintos: o codec escreve a identidade na URL, o locator descobre onde ela está.

typescript

Feeds infinitos. locate é síncrono, então só responde pelos posts já carregados — um link compartilhado para o post 400 de um feed que carregou 20 volta vazio. locateAsync é o plano B, chamado somente quando locate falha.

Atalho

Vai endereçar pelo id do item? Dispense o codec e o locator escritos à mão — passe locateAsync direto para urlStableIdKey({ items, locateAsync }) (ele busca quando falha e então devolve o índice). A versão mais completa abaixo serve para endereçar por outro campo, ou para ter controle total.

typescript
  • Enquanto locateAsync está pendente, o player fica fechado e o parâmetro é deixado em paz, então o link direto sobrevive à busca. Um null ou uma rejeição derrubam o parâmetro.
  • Uma resposta que chega depois de a URL ter mudado, depois de um fechamento ou depois da desmontagem é descartada — uma busca lenta não pode abrir um slide que ninguém pediu.
  • Nada é renderizado enquanto a busca corre; esse estado de carregamento já pertence à página, então desenhe o seu próprio esqueleto.
  • Não existe tempo limite — o player não tem como saber o tamanho do feed. Termine com null quando a paginação se esgotar, ou o overlay fica fechado para sempre.

Referência da API

Props do ReelPlayerOverlay

ReelPlayerOverlayProps

PropTipoPadrãoDescrição
ariaLabelstring'Video player'Rótulo acessível da região do diálogo; anunciado pelos leitores de tela quando o overlay abre
aspectRationumber9 / 16Proporção entre largura e altura do contêiner no desktop. No celular vale a tela inteira.
contentT[] (extends BaseContentItem)obrigatórioArray de itens de conteúdo a exibir no player
enableNavKeysbooleantrueLiga a navegação pelas setas do teclado
enableWheelbooleantrueLiga a navegação pela roda do mouse
initialIndexnumber0Índice, começando em zero, do item visível no início
initialInnerIndexnumber0Índice da mídia interna em que abrir, apenas para o post visível no início — permite que uma URL de dois eixos aponte direto para uma imagem específica de um post com várias mídias. Ignorado assim que o leitor navega.
isOpenbooleanobrigatórioControla a visibilidade do overlay; com false, o overlay sai do DOM
loopbooleanfalseLiga o ciclo infinito entre os slides
swipeDistanceFactornumber0.12Fração mínima da distância de deslize para trocar de slide
timeline'auto' | 'always' | 'never''auto'Estratégia que libera a barra de reprodução embutida. 'auto' só renderiza para vídeos mais longos que timelineMinDurationSeconds; 'always' renderiza sempre que o slide ativo tiver vídeo; 'never' desliga a barra embutida (use o slot #timeline para substituí-la inteiramente).
timelineMinDurationSecondsnumber30Duração mínima do vídeo, em segundos, para que timeline='auto' renderize a barra embutida. Clipes curtos em repetição abaixo desse limite ficam sem barra.
transitionDurationnumber300Duração da animação do slide em ms
wheelDebounceMsnumber200Duração do debounce dos eventos de roda em ms

Props do ReelPlayerUrlOverlay

ReelPlayerUrlOverlayProps

Aceita todas as props acima, menos is-open, substituída por controller. initial-index é ignorada — quem escolhe o slide é a posição do controlador, então um valor passado ao lado dela é sobrescrito a cada abertura.

PropTipoPadrãoDescrição
controllerUrlStateControllerobrigatórioControlador vindo de useOverlayUrlState. A position dele decide se o overlay está aberto e qual slide aparece; o overlay escreve de volta por ele na troca de slide e no fechamento.

Eventos

EventoCargaDescrição
@api-readyReelPlayerApiEmitido assim que o slider fica pronto, expondo a API imperativa
@closevoidEmitido quando o player fecha
@slide-changenumberEmitido com o novo índice do slide ativo depois de uma troca
@inner-slide-changeouter: number, inner: numberEmitido quando o índice da mídia interna do post ativo muda — na navegação interna e na ativação externa (o índice interno atual do post ativado, 0 para mídia única).
@update:is-openbooleanEmitido no fechamento; é o que viabiliza o `v-model:is-open`

v-model:is-open

Use v-model:is-open para conduzir o overlay com uma única ligação. O padrão antigo de :is-open com @close continua funcionando, se você precisar do evento explícito.

vue

Tipos

ContentItem

CampoTipoDescrição
idstringIdentificador único
mediaMediaItem[]Uma ou mais mídias (imagem ou vídeo)
author{ name: string; avatar?: string }Autor mostrado na camada padrão do slide
descriptionstring?Texto da legenda
likesnumber?Contagem de curtidas

TimelineBarProps

typescript

TimelineSlotScope<T>

typescript

MediaItem

CampoTipoDescrição
idstringIdentificador único
type'image' | 'video'Tipo da mídia
srcstringURL do arquivo de mídia
posterstring?URL da miniatura de pôster, para itens de vídeo
aspectRationumberProporção entre largura e altura. Valores < 1 = vertical (cover), ≥ 1 = horizontal (contain).

Subcomponentes

Use-os dentro dos seus templates de #controls, #slide ou #slideOverlay. Repasse as dimensões e os callbacks do escopo do slot para que a reprodução automática, a captura de pôster e a sincronia do som continuem funcionando.

CloseButton

Botão circular de fechar isolado, já com o estilo do reel player. Use dentro de #controls.

vue

SoundButton

Botão de mudo. Renderize-o dentro de um SoundProvider (o ReelPlayerOverlay fornece um). Fica escondido quando o slide ativo não tem vídeo.

vue

TimelineBar

Barra de reprodução padrão. Lê o TimelineProvider mais próximo (montado automaticamente dentro do ReelPlayerOverlay) e desenha a trilha, os trechos em buffer, o preenchimento do progresso e a pílula de arrasto. Personalize pelas propriedades --rk-reel-timeline-*, ou substitua pelo slot #timeline.

vue

SlideOverlay

A camada em gradiente padrão, que mostra autor, descrição e curtidas. Aparece quando o conteúdo traz esses campos. Substitua-a ou esconda-a pelo slot #slideOverlay.

vue

ImageSlide

Slide de imagem com carregamento tardio e object-fit: cover por padrão. Componha-o dentro do slot #slide para personalizar a exibição da imagem mantendo o comportamento embutido.

vue

VideoSlide

Slide de vídeo apoiado em um elemento <video> compartilhado. Cuida da continuidade do som no iOS, dos quadros de pôster e da memória de posição. Renderize-o dentro de um SoundProvider (o ReelPlayerOverlay fornece um).

vue
Compondo slides próprios

Use #slide com ImageSlide / VideoSlide para personalizar a exibição da mídia mantendo todo o comportamento embutido (reprodução automática, captura de pôster, sincronia do som).

vue

Carregamento de conteúdo e tratamento de erros

O player acompanha o carregamento e os erros de cada slide. Uma animação de onda aparece enquanto o conteúdo carrega; mídias quebradas mostram um ícone de erro. O player guarda em cache as URLs que falharam, então reabrir um slide quebrado pula a nova tentativa.

Callbacks de ciclo de vida

Ao usar o slot #slide, chame estes callbacks, vindos do escopo do slot, para comandar o indicador de carregamento:

CallbackQuando chamar
onReadyA imagem carregou ou o vídeo começou a tocar. Limpa os estados de carregamento e erro.
onWaitingO vídeo está carregando no meio da reprodução. Mostra o indicador de carregamento.
onErrorO conteúdo não carregou. Mostra a camada de erro e registra a URL como quebrada.
vue

Interface própria de carregamento e erro

Troque a animação de onda e o ícone de erro padrão pelos slots #loading e #error:

vue

Linha do tempo

O overlay desenha uma barra de reprodução embutida sobre o vídeo ativo. Libere-a pela prop timeline: 'auto' (padrão) aparece sempre que a mídia ativa for um vídeo mais longo que timelineMinDurationSeconds (30 por padrão), 'always' sempre que houver um vídeo ativo, 'never' para desligar. Para uma barra totalmente sua, use o slot #timeline; o escopo dele expõe um timelineState apoiado no TimelineController por baixo.

vue

Personalize pelas propriedades CSS --rk-reel-timeline-*.

Contexto de som

O ReelPlayerOverlay monta um SoundProvider na raiz, então qualquer componente renderizado lá dentro pode ler ou alternar o estado de mudo por useSoundState. O composable é reexportado por @reelkit/vue-reel-player, então você não precisa de um import separado de @reelkit/vue.

vue

Dentro do player, o slot #controls também expõe soundState no seu escopo. Prefira essa via quando precisar do estado apenas no template dos controles.

Classes CSS

As classes CSS são simples (sem escopo). Uma folha de estilo carregada depois de @reelkit/vue-reel-player/styles.css pode sobrescrever qualquer uma delas com um seletor de especificidade maior. Para mudar cor, tamanho e z-index, use as propriedades CSS da seção Temas, abaixo.

ClasseComponenteDescrição
.rk-reel-overlayOverlayFundo fixo em tela cheia (cor de fundo, z-index)
.rk-reel-containerOverlayContêiner do player (posição, transbordo)
.rk-reel-loaderOverlayCamada da animação de onda do carregamento
.rk-reel-media-errorOverlayCamada do estado de erro (ícone e texto centralizados)
.rk-reel-media-error-textOverlayTexto da mensagem de erro
.rk-reel-buttonControlsBotão circular de ícone compartilhado (fechar, som, setas)
.rk-reel-close-btnControlsBotão de fechar
.rk-reel-sound-btnControlsBotão de alternar o som
.rk-reel-nav-arrowsNavigationContêiner das setas, só no desktop (escondido abaixo de 768px)
.rk-reel-nav-buttonNavigationCada seta de avançar e voltar
.rk-reel-slide-wrapperSlideInvólucro em volta da mídia e da camada sobreposta
.rk-reel-slide-overlaySlideOverlayContêiner da camada em gradiente
.rk-reel-slide-overlay-authorSlideOverlayLinha do autor (avatar e nome)
.rk-reel-slide-overlay-avatarSlideOverlayImagem do avatar do autor
.rk-reel-slide-overlay-nameSlideOverlayTexto do nome do autor
.rk-reel-slide-overlay-descriptionSlideOverlayTexto da descrição
.rk-reel-slide-overlay-likesSlideOverlayLinha das curtidas (coração e contagem)
.rk-reel-video-containerVideoSlideInvólucro do vídeo (cor de fundo, transbordo)
.rk-reel-video-elementVideoSlideO elemento <video>
.rk-reel-video-posterVideoSlideImagem de pôster (some ao dar play)
.rk-reel-video-poster.rk-visibleVideoSlideModificador de estado aplicado ao pôster enquanto o vídeo está pausado ou carregando
.rk-reel-nested-indicatorNestedSliderPontinhos de paginação sob os slides com várias mídias (a posição muda entre desktop e toque)
.rk-reel-nested-navNestedSliderSetas do carrossel horizontal (escondidas abaixo de 768px)
.rk-reel-nested-nav-nextNestedSliderPosição da seta de avançar aninhada
.rk-reel-nested-nav-prevNestedSliderPosição da seta de voltar aninhada
.rk-reel-timelineTimelineBarInvólucro da barra de arrasto. Reaproveite nas raízes do seu slot `#timeline` para herdar o posicionamento rente à base, o respiro da área segura e o espaço reservado para a camada do slide em dispositivos de toque.
.rk-reel-timeline-trackTimelineBarTrilha (trecho ainda não reproduzido)
.rk-reel-timeline-bufferedTimelineBarCamada dos trechos em buffer
.rk-reel-timeline-fillTimelineBarPreenchimento do que já foi reproduzido
.rk-reel-timeline-cursorTimelineBarPílula da alça de arrasto (flutua acima da trilha)

Temas

Cada cor, tamanho, z-index e transição vive em uma propriedade CSS. Sobrescreva uma ou várias em :root (ou em qualquer ancestral do overlay) para trocar o tema sem mexer no código dos componentes. Os tokens são os mesmos de @reelkit/react-reel-player, então as sobrescritas valem para os dois bindings.

TokenPadrãoO que controla
--rk-reel-overlay-bg#000Cor do fundo em tela cheia
--rk-reel-overlay-z1000z-index do overlay
--rk-reel-button-bgrgba(0, 0, 0, 0.5)Fundo padrão dos botões circulares
--rk-reel-button-bg-hoverrgba(255, 255, 255, 0.1)Fundo das setas de navegação (e estado básico ao passar o mouse)
--rk-reel-button-bg-hover-strongrgba(255, 255, 255, 0.2)Fundo das setas ao passar o mouse
--rk-reel-button-fg#fffCor do ícone do botão
--rk-reel-button-size44pxLargura e altura do botão
--rk-reel-button-radius50%Arredondamento do botão
--rk-reel-ui-z10z-index de fechar, som e navegação
--rk-reel-edge-padding16pxRecuo da borda para fechar, som e setas
--rk-reel-nav-gap8pxEspaço entre as setas empilhadas
--rk-reel-transition0.2sDuração da transição ao passar o mouse
--rk-reel-loader-colorrgba(255, 255, 255, 0.12)Cor do gradiente da animação de onda
--rk-reel-loader-duration1.8sDuração da animação de onda
--rk-reel-error-fgrgba(255, 255, 255, 0.4)Cor do ícone e do texto de erro
--rk-reel-slide-overlay-bglinear-gradient(transparent, rgba(0, 0, 0, 0.7))Gradiente que escurece o fundo da legenda
--rk-reel-slide-overlay-padding48px 16px 16pxRespiro interno da legenda
--rk-reel-slide-overlay-name-color#fffCor do nome do autor
--rk-reel-video-bg#000Cor das faixas em volta do <video>
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)Fundo das setas aninhadas
--rk-reel-nested-button-size36pxTamanho das setas aninhadas
--rk-reel-timeline-trackrgba(255, 255, 255, 0.22)Fundo da trilha (trecho ainda não reproduzido)
--rk-reel-timeline-bufferedrgba(255, 255, 255, 0.4)Cor dos trechos em buffer
--rk-reel-timeline-fill#fffCor do preenchimento já reproduzido
--rk-reel-timeline-cursor#fffCor da pílula da alça de arrasto
--rk-reel-timeline-height3pxAltura da trilha em repouso
--rk-reel-timeline-height-active6pxAltura da trilha ao passar o mouse, no foco ou durante o arrasto
--rk-reel-timeline-cursor-width10pxLargura da pílula em repouso
--rk-reel-timeline-cursor-width-active14pxLargura da pílula durante o arrasto
--rk-reel-timeline-cursor-height24pxAltura da pílula em repouso
--rk-reel-timeline-cursor-height-active32pxAltura da pílula durante o arrasto
--rk-reel-timeline-transition0.15s ease-outAnimação de crescer e encolher da trilha e da pílula

Cole o trecho abaixo em uma folha de estilo carregada depois de @reelkit/vue-reel-player/styles.css.

css

Acessibilidade

A raiz do overlay é um diálogo modal (role="dialog", aria-modal="true"). Defina a prop aria-label para mudar o que o leitor de tela anuncia; o padrão é "Video player". Cada slide carrega role="group", aria-roledescription="slide" e aria-label="Slide N of M".

O overlay captura o foco ao abrir e o devolve ao gatilho ao fechar. Tab e Shift+Tab percorrem os elementos focáveis lá dentro; um foco que escapa (clique fora, foco por código) é trazido de volta. Implementado com captureFocusForReturn e createFocusTrap, de @reelkit/core.

Atalhos de teclado

TeclaAção
ArrowUpSlide anterior
ArrowDownPróximo slide
ArrowLeftMídia anterior (carrossel aninhado)
ArrowRightPróxima mídia (carrossel aninhado)
EscapeFecha o player