El motor de @reelkit/react-stories-player. TypeScript puro, sin dependencias de framework. Úsalo para crear reproductores de stories con Angular, Vue o JavaScript sin framework.
Independiente del framework
TypeScript puro, sin dependencias de frameworks de DOM
Navegación en dos niveles
Grupos y stories dentro de cada grupo
Temporizador con RAF
Avance automático con requestAnimationFrame, con pausa y reanudación
Progreso en Canvas
Barra de progreso segmentada lista para Retina, con ventana deslizante
Zonas de toque
Detección configurable de toques a izquierda y derecha
Señales reactivas
Construido sobre las primitivas de señales de @reelkit/core
Instalación
bash
npm i @reelkit/stories-core
Controlador de Stories
createStoriesController(config, events?) gestiona la navegación entre grupos y stories. Sigue el estado de pausa y reanudación, recuerda la última story vista de cada grupo y lanza callbacks en cada transición.
Configuración (StoriesControllerConfig)
Propiedad
Tipo
Por defecto
Descripción
groupCount
number
obligatorio
Número total de grupos de stories
storyCounts
number[]
obligatorio
Número de stories de cada grupo
initialGroupIndex
number
0
Índice del grupo inicial
initialStoryIndex
number
resumeStoryIndex(initialGroupIndex), si no 0
Índice de la story inicial dentro del grupo. Si lo indicas, se impone a lo que se haya recordado; si no, el grupo inicial se reanuda igual que los demás.
defaultImageDuration
number
5000
Duración por defecto del avance automático en las stories con imagen, en ms
resumeStoryIndex
(groupIndex: number) => number
undefined
Story con la que se abre un grupo no visitado, limitada a una story que el grupo tenga
Eventos (StoriesControllerEvents)
Evento
Tipo
Descripción
onStoryChange
(groupIndex, storyIndex) => void
Se lanza al cambiar la story activa
onGroupChange
(groupIndex) => void
Se lanza al cambiar el grupo activo
onStoryViewed
(groupIndex, storyIndex) => void
Se lanza cuando una story se hace visible
onStoryComplete
(groupIndex, storyIndex) => void
Se lanza al terminar el temporizador de una story (antes de avanzar)
onComplete
() => void
Se lanza al terminar la última story del último grupo
onClose
() => void
Se lanza cuando el overlay debe cerrarse
Estado (señales reactivas)
Señal
Tipo
Descripción
state.activeGroupIndex
Signal<number>
Índice del grupo activo
state.activeStoryIndex
Signal<number>
Índice de la story activa dentro del grupo
state.isPaused
Signal<boolean>
Indica si el avance automático está en pausa
Métodos
Método
Tipo
Descripción
nextStory()
() => void
Avanza dentro del grupo; al llegar al final pasa al grupo siguiente
prevStory()
() => void
Retrocede dentro del grupo; al llegar al principio pasa al grupo anterior
nextGroup()
() => void
Cambia al grupo siguiente y retoma la última story vista
prevGroup()
() => void
Cambia al grupo anterior y retoma la última story vista
goToGroup(index)
(number) => void
Salta a un grupo concreto por su índice
pause()
() => void
Pausa el avance automático
resume()
() => void
Reanuda el avance automático
onStoryTimerComplete()
() => void
Se llama al terminar el temporizador; lanza onStoryComplete y después avanza
getLastStoryIndex(groupIndex)
(number) => number
Dónde se abre un grupo: la story en la que se dejó en esta sesión o, si no, la que indique resumeStoryIndex
reportInitialView()
() => void
Marca como vista, una sola vez, la story con la que se abrió el reproductor. Llámalo después de montar, no durante el renderizado.
Ejemplo
typescript
import { createStoriesController, createTimerController,} from '@reelkit/stories-core';import { reaction } from '@reelkit/core';const groups = [ { stories: ['s1', 's2', 's3'] }, { stories: ['s4', 's5'] },];const controller = createStoriesController( { groupCount: groups.length, storyCounts: groups.map((g) => g.stories.length), defaultImageDuration: 5000, }, { onStoryChange(groupIndex, storyIndex) { console.log('Story changed:', groupIndex, storyIndex); }, onComplete() { console.log('All stories viewed'); }, onClose() { console.log('Overlay closed'); }, },);// Wire up a timer for auto-advanceconst timer = createTimerController({ duration: 5000, onComplete: () => controller.onStoryTimerComplete(),});// React to story changes and restart the timerconst dispose = reaction( () => [ controller.state.activeGroupIndex, controller.state.activeStoryIndex, ], () => timer.start(),);// Start playbacktimer.start();// Navigationcontroller.nextStory();controller.pause();controller.resume();// Cleanupdispose();timer.dispose();
Controlador del temporizador
createTimerController(config) hace avanzar las stories con un bucle de requestAnimationFrame. La señal de progreso (de 0 a 1) alimenta la barra de progreso. Pausar y reanudar conserva la posición exacta.
Configuración (TimerControllerConfig)
Propiedad
Tipo
Por defecto
Descripción
duration
number
obligatorio
Duración por defecto en milisegundos
onComplete
() => void
undefined
Se llama cuando el temporizador llega al 100 %
Estado
Señal
Tipo
Descripción
progress
Signal<number>
Señal de progreso (de 0 a 1)
isRunning
Signal<boolean>
Indica si el temporizador está en marcha
Métodos
Método
Tipo
Descripción
start(duration?)
(number?) => void
Inicia (o reinicia) el temporizador con una duración opcional distinta
pause()
() => void
Congela el progreso en la posición actual
resume()
() => void
Continúa desde la posición congelada
reset()
() => void
Vuelve el progreso a 0 y lo detiene
dispose()
() => void
Libera los recursos
Ejemplo
typescript
import { createTimerController } from '@reelkit/stories-core';import { reaction } from '@reelkit/core';const timer = createTimerController({ duration: 5000, onComplete: () => console.log('Timer finished!'),});// Observe progress (0 to 1)const dispose = reaction( () => [timer.progress], () => { console.log('Progress:', timer.progress.value); },);// Start with default durationtimer.start();// Or override duration for a specific storytimer.start(8000);// Pause/resume preserves exact positiontimer.pause();timer.resume();// Reset to 0timer.reset();// Cleanupdispose();timer.dispose();
Renderizador de progreso en Canvas
createCanvasProgressRenderer(config?) dibuja barras de progreso segmentadas en un canvas. Se adapta a pantallas Retina, mide su contenedor con ResizeObserver y usa una ventana deslizante cuando los segmentos no caben.
Configuración (CanvasProgressRendererConfig)
Propiedad
Tipo
Por defecto
Descripción
gap
number
2
Separación en píxeles entre segmentos
barHeight
number
2
Altura de la barra en píxeles
minSegmentWidth
number
8
Ancho mínimo de un segmento antes de que entre en juego la ventana deslizante
bgColor
string
'rgba(255,255,255,0.3)'
Color de fondo de los segmentos sin rellenar
fillColor
string
'#ffffff'
Color de relleno de los segmentos completados o activos
Métodos
Miembro
Tipo
Descripción
attach(canvas)
(HTMLCanvasElement) => void
Se conecta a un elemento canvas e inicia ResizeObserver en su padre
draw(totalStories, activeIndex, progress)
(number, number, number) => void
Dibuja la barra de progreso para el estado indicado
createStoriesViewedState(controller, groups) lee un ViewedStateController del core con los términos que usa un reproductor (grupos, autores, índices de story) sin que el almacén conozca ninguno de ellos. Devuelve un StoriesViewedState. Crea el almacén con la misma clave que usa la barra de direcciones, con seguimiento por grupo, y una entrada guardada se leerá exactamente igual que el parámetro de un enlace compartido.
Parámetro
Tipo
Descripción
controller
ViewedStateController<TwoAxisPosition>
Almacén del core creado con la misma clave que la barra de direcciones y con twoAxisViewedTracking para que cada grupo tenga su propia entrada
groups
() => StoriesGroup<T>[]
Lee los grupos actuales. Es un getter, así que un feed que carga páginas o se reordena después de configurarse se mide en el momento de la llamada.
StoriesViewedState
Método
Tipo
Descripción
viewedCounts()
() => Map<string, number>
Stories vistas por grupo, con el id del autor como clave: la forma que StoriesRingList espera como viewedState
resumeStoryIndex(groupIndex)
(number) => number
La primera story sin ver, o 0 cuando el grupo ya se ha visto hasta el final
markViewed(groupIndex, storyIndex)
(number, number) => void
Registra una story como vista. Conéctalo a onStoryViewed.
Ejemplo
typescript
import { createStoriesViewedState } from '@reelkit/stories-core';import { createViewedStateController, urlStableIdTwoAxisKey, twoAxisViewedTracking,} from '@reelkit/core';const seen = createViewedStateController({ storageKey: 'stories-seen', ...urlStableIdTwoAxisKey({ outerItems, innerItems }), ...twoAxisViewedTracking,});seen.attach();const viewed = createStoriesViewedState(seen, () => groups);viewed.viewedCounts(); // Map { 'user_42' => 2 }viewed.resumeStoryIndex(0); // 2 — the first story not yet seenviewed.markViewed(0, 2); // furthest point wins; a rewatch never rewinds
Una entrada indica la story más lejana a la que se llegó, no un recuento de visualizaciones, así que una clave por id mantiene el punto de un grupo aunque el feed se reordene, mientras que quitar una story del medio de un grupo acorta su cuenta y vuelve a encender su anillo.
Funciones de utilidad
Funciones puras para detectar las zonas de toque y calcular la barra de progreso.
Función
Tipo
Descripción
getTapAction(tapX, containerWidth, splitRatio?)
(number, number, number?) => 'prev' | 'next'
Decide según la posición si un toque lanza 'prev' o 'next'. El splitRatio por defecto es 0.3.
getSegments(totalStories, activeIndex, progress)
(number, number, number) => SegmentState[]
Calcula el estado y el porcentaje de relleno de cada segmento de una barra de progreso