Reel Player для Vue
Повноекранний вертикальний медіаплеєр у стилі Instagram чи TikTok для Vue 3 на основі @reelkit/vue-reel-player.
Features
Встановлення
npm install @reelkit/vue-reel-player @reelkit/vue lucide-vue-nextІмпортуйте таблицю стилів один раз у точці входу застосунку (або в будь-якому компоненті):
import '@reelkit/vue-reel-player/styles.css';Icons
Стандартні елементи керування використовують lucide-vue-next для іконок (закриття, звук, стрілки навігації). Якщо ви віддаєте перевагу іншій бібліотеці іконок, скористайтеся слотами #controls та #navigation з областю видимості, щоб передати власні.
Vue 3.3 або новіша
Оверлеї — узагальнені компоненти, які Vue підтримує починаючи з 3.3. На старішій Vue рантайм відкидає їхні пропси й події, тож пакет вимагає vue 3.3 або новішу як peer-залежність.
Швидкий старт
Покажіть сітку мініатюр і відкривайте оверлей на натиснутому індексі. Прив’язка v-model:is-open означає, що батьківський ref лишається синхронним, коли користувач закриває плеєр кнопкою, жестом або клавішею Escape.
<script setup lang="ts">
import { ref } from 'vue';
import { ReelPlayerOverlay, type ContentItem } from '@reelkit/vue-reel-player';
import '@reelkit/vue-reel-player/styles.css';
const content: ContentItem[] = [
{
id: '1',
media: [{
id: 'v1',
type: 'video',
src: '/cdn/samples/videos/video-01.mp4',
poster: '/cdn/samples/videos/video-poster-01.jpg',
aspectRatio: 16 / 9,
}],
author: { name: 'Alex Johnson', avatar: '/cdn/samples/avatars/avatar-01.jpg' },
likes: 1234,
description: 'Amazing sunset vibes',
},
{
id: '2',
media: [{
id: 'img1',
type: 'image',
src: '/cdn/samples/images/image-01.jpg',
aspectRatio: 2 / 3,
}],
author: { name: 'Sarah Miller', avatar: '/cdn/samples/avatars/avatar-02.jpg' },
likes: 5678,
description: 'Nature at its finest',
},
{
id: '3',
media: [{
id: 'v2',
type: 'video',
src: '/cdn/samples/videos/video-02.mp4',
poster: '/cdn/samples/videos/video-poster-02.jpg',
aspectRatio: 16 / 9,
}],
author: { name: 'Mike Chen', avatar: '/cdn/samples/avatars/avatar-03.jpg' },
likes: 3456,
description: 'Adventure awaits',
},
];
const isOpen = ref(false);
const startIndex = ref(0);
function openAt(i: number) {
startIndex.value = i;
isOpen.value = true;
}
</script>
<template>
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:6px">
<button
v-for="(item, i) in content"
:key="item.id"
@click="openAt(i)"
style="aspect-ratio:9/16;cursor:pointer;overflow:hidden;padding:0;border:0"
>
<img
:src="item.media[0].poster || item.media[0].src"
style="width:100%;height:100%;object-fit:cover"
/>
</button>
</div>
<ReelPlayerOverlay
v-model:is-open="isOpen"
:content="content"
:initial-index="startIndex"
/>
</template>Слоти з областю видимості
Вісім слотів з областю видимості дають замінити будь-яку частину інтерфейсу плеєра. Кожен отримує строго типізований об’єкт області. Слоти, які ви не передали, лишаються стандартними.
| Slot | Scope | Опис |
|---|---|---|
| #controls | { item, soundState, activeIndex, content, onClose } | Власна загальна смуга керування (закриття, звук, поділитися тощо) |
| #error | { item, activeIndex, innerActiveIndex } | Власний індикатор помилки (замінює стандартний значок) |
| #loading | { item, activeIndex, innerActiveIndex } | Власний індикатор завантаження (замінює стандартну хвилю) |
| #navigation | { item, activeIndex, count, onPrev, onNext } | Власні стрілки навігації вперед і назад (десктоп) |
| #nestedNavigation | { media, activeIndex, count, onPrev, onNext } | Власні стрілки для внутрішнього горизонтального слайдера |
| #nestedSlide | { item, media, index, size, isActive, isInnerActive, slideKey, defaultContent, onReady, onWaiting, onError } | Власний вміст слайда всередині внутрішнього горизонтального слайдера |
| #slide | { item, index, size, isActive, slideKey, defaultContent, onReady, onWaiting, onError } | Повністю власний вміст слайда (якщо не задано, лишається стандартний) |
| #slideOverlay | { item, index, isActive } | Оверлей для кожного слайда (дані автора, лайки, опис тощо) |
| #timeline | { item, activeIndex, timelineState, defaultContent } | Власна смуга таймлайну відтворення. Викликається лише тоді, коли за вбудованим правилом (режим timeline плюс мінім альна тривалість) вивелася б стандартна смуга; логіка auto/always/never та сама. Через defaultContent() можна загорнути вбудований <TimelineBar />. |
<script setup lang="ts">
import { ref } from 'vue';
import {
ReelPlayerOverlay,
CloseButton,
SoundButton,
type ContentItem,
type SlideOverlaySlotScope,
type ControlsSlotScope,
} from '@reelkit/vue-reel-player';
const isOpen = ref(false);
const content = ref<ContentItem[]>([/* ... */]);
</script>
<template>
<ReelPlayerOverlay v-model:is-open="isOpen" :content="content">
<!-- Custom per-slide overlay: branded caption -->
<template #slideOverlay="{ item, isActive }: SlideOverlaySlotScope">
<div v-if="isActive" style="position:absolute;bottom:80px;left:16px;color:#fff">
<div style="display:flex;align-items:center;gap:8px">
<img :src="item.author.avatar" style="width:40px;height:40px;border-radius:50%" />
<span style="font-weight:600">{{ item.author.name }}</span>
</div>
<p style="margin-top:8px">{{ item.description }}</p>
</div>
</template>
<!-- Custom global controls -->
<template #controls="{ onClose }: ControlsSlotScope">
<div style="position:absolute;top:16px;right:16px;display:flex;gap:8px">
<SoundButton />
<CloseButton :on-click="onClose" />
</div>
</template>
<!-- Custom playback timeline -->
<template #timeline="{ timelineState }: TimelineSlotScope">
<CustomTimelineBar :state="timelineState" />
</template>
</ReelPlayerOverlay>
</template>Власний таймлайн
Замініть вбудовану смугу відтворення власним інтерфейсом перемотування через слот #timeline . Слот спрацьовує лише тоді, коли за правилами показу оверлей вивів би стандартну смугу (та сама логіка timeline плюс timelineMinDurationSeconds), so you don't re-implement it. Reuse the .rk-reel-timeline на своєму корені, щоб успадкувати притиснення до низу, відступи безпечної зони та проміжок на дотикових пристроях.
<script setup lang="ts">
import { shallowRef, onMounted, onBeforeUnmount } from 'vue';
import {
ReelPlayerOverlay,
type TimelineSlotScope,
} from '@reelkit/vue-reel-player';
import { toVueRef, type TimelineController } from '@reelkit/vue';
const trackRef = shallowRef<HTMLDivElement | null>(null);
let dispose: (() => void) | null = null;
const bind = (state: TimelineController) => {
if (trackRef.value) dispose = state.bindInteractions(trackRef.value);
};
onBeforeUnmount(() => dispose?.());
</script>
<template>
<ReelPlayerOverlay :is-open="open" :content="items" timeline="always">
<template #timeline="{ timelineState }: TimelineSlotScope">
<div class="rk-reel-timeline" style="padding: 0 16px" @vue:mounted="bind(timelineState)">
<div ref="trackRef" role="slider" style="height:6px;background:rgba(255,255,255,0.2)">
<div :style="{
width: (timelineState.progress.value * 100) + '%',
height: '100%',
background: 'linear-gradient(90deg, #6366f1, #ec4899)',
}" />
</div>
</div>
</template>
</ReelPlayerOverlay>
</template>Власні типи вмісту
ReelPlayerOverlay і ReelPlayerUrlOverlay узагальнені за формою вашого елемента вмісту, як і React-плеєр. Розширте BaseContentItem, щоб узяти будь-яку модель даних: тип елемента виводиться з content, тож стрічка, що не відповідає BaseContentItem, не проходить перевірку типів. У Vue слоти не можуть нести цей тип, тому їхні області видимості лишаються нетипізованими, доки ви їх не анотуєте. Імпортуйте відповідний тип області слота й анотуйте деструктуризацію, щоб додаткові поля лишалися типізованими всередині шаблону. Анотацію ніщо не звіряє з content, тож тримайте їх узгодженими:
<script setup lang="ts">
import { ref } from 'vue';
import {
ReelPlayerOverlay,
type BaseContentItem,
type SlideOverlaySlotScope,
} from '@reelkit/vue-reel-player';
interface MyItem extends BaseContentItem {
title: string;
category: 'video' | 'photo';
}
const open = ref(false);
const items: MyItem[] = [/* ... */];
</script>
<template>
<ReelPlayerOverlay v-model:is-open="open" :content="items">
<template #slideOverlay="{ item }: SlideOverlaySlotScope<MyItem>">
<div class="my-overlay">
<h2>{{ item.title }}</h2>
<span>{{ item.category }}</span>
</div>
</template>
</ReelPlayerOverlay>
</template>Той самий патерн працює для будь-якого іншого слота. Імпортуйте відповідний тип області (SlideSlotScope, ControlsSlotScope, NavigationSlotScope, NestedSlideSlotScope, LoadingSlotScope) and annotate the destructure.
Стан в URL
Подивитися демо наживо →Build a controller with useOverlayUrlState from @reelkit/vue і передайте його в ReelPlayerUrlOverlay as controller: плеєр належить адресному рядку, тож він відкривається, коли параметр назива є слайд, і закривається, коли параметр зникає. Відкриття додає один запис в історію, а кожна зміна слайда його замінює, тож гортання стрічки не додає записів і один крок назад завжди виводить. Глибина URL залежить від ключа контролера: одновісний urlIndexKey адресує лише допис (?reel=3), a two-axis urlIndexTwoAxisKey несе ще й індекс внутрішнього медіа мультимедійного допису (?reel=3.2); pick one key per app, the two wire shapes do not cross-decode. It is a separate component from ReelPlayerOverlay, so each carries exactly one open-state driver — the is-open модель або url controller, never both.
Вбудовані клавіші
Слайди можна адресувати вбудованим ключем — розгорніть urlIndexKey (за позицією) або urlStableIdKey (за стабільним id) into the controller — both re-exported from @reelkit/vue. See the посібник зі стану в URL та API ядра.
Застосунок із роутером має передати адаптер на базі роутера, щоб той лишався єдиним джерелом правди про навігацію: запис в історію повз нього лишає його місцеположення застарілим і втрачає параметр на наступній навігації. useVueRouterUrlAdapter from @reelkit/vue/vue-router-url-adapter — готовий адаптер для Vue Router.
<script setup lang="ts">
import { ReelPlayerUrlOverlay, type ContentItem } from '@reelkit/vue-reel-player';
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/vue';
import { useVueRouterUrlAdapter } from '@reelkit/vue/vue-router-url-adapter';
import '@reelkit/vue-reel-player/styles.css';
const props = defineProps<{ content: ContentItem[] }>();
const reel = useOverlayUrlState({
param: 'reel',
adapter: useVueRouterUrlAdapter(),
...urlIndexKey(() => props.content.length),
});
</script>
<template>
<!-- Opening is a link — the overlay reads the URL and opens itself. -->
<RouterLink v-for="(post, i) in props.content" :key="post.id" :to="`?reel=${i}`">
<img :src="post.media[0].src" />
</RouterLink>
<ReelPlayerUrlOverlay :controller="reel" :content="props.content" />
</template>Full useOverlayUrlState опції — у довіднику API для Vue.
- Відкриття додає один запис в історію. Свайп стрічки замінює його, тож N свайпів не додають записів, і один крок назад завжди виходить із плеєра. «Назад» закриває, а не гортає слайди.
- «Назад» закриває лише тоді, коли плеєр відкрили всередині застосунку — посилання додало запис. За надісланим посиланням у новій вкладці історії позаду немає, тож кнопка «назад» виведе із сайту; кнопка ✕ або Escape прибирає параметр на місці й лишає вас на сторінці.
- Пряме посилання
?reel=3відкриває плеєр одразу на цьому слайді. - Параметр, який не називає жодного слайда — застаріла закладка, змінене вручну значення, — прибирається з URL, а не лишає в адресному рядку слайд, який не відкриється.
- Глибина URL залежить від ключа контролера: одна вісь для самого допису або дві (
urlIndexTwoAxisKey) to also carry a multi-media post’s inner image index. Pick one key per app; the shapes do not cross-decode.
Один ключ чи два — оберіть глибину URL
Той самий ReelPlayerOverlay працює з обома формами; він розрізняє їх під час виконання за position контролера, тож жодного пропса режиму немає. Ключ обирайте, коли будуєте контролер:
| Key | Wire | Carries |
|---|---|---|
urlIndexKey(…) | ?reel=3 | Лише вертикальний допис. |
urlIndexTwoAxisKey(…) | ?reel=3.2 | Допис та та індекс внутрішнього медіа каруселі. |
Дві форми запису навмисно різні — двовісний ключ строго з крапкою (3.0, never a bare 3), so a bare one-axis link does not cross-decode. Switching an app between keys therefore invalidates any previously shared links. Pick one shape and keep it.
import { useOverlayUrlState, urlIndexTwoAxisKey } from '@reelkit/vue';
const reel = useOverlayUrlState({
param: 'reel',
...urlIndexTwoAxisKey({
outerCount: () => content.value.length,
innerCounts: () => content.value.map((post) => post.media.length),
}),
});
// A link now names both axes: post 3, inner media 2 — ?reel=3.2Стабільні посилання. Індекс адресує за позицією, тож збережений у закладках ?reel=3 відкриє інший допис, щойно стрічку перевпорядкують — а для стрічки це радше правило, ніж виняток. urlStableIdKey адресує за стабільним id, scanning the live feed — one call covers the common case.
const reel = useOverlayUrlState({
param: 'reel',
...urlStableIdKey({ items: () => content.value }),
});Pass hashCodec: base64UrlCodec щоб закодувати id в URL у base64url — оборотне маскування, а не криптографічний хеш.
Адресуєте за іншим полем ( slug), or page an infinite feed with locateAsync, and build the codec/locator самі. Дві окремі задачі: codec записує ідентичність в URL, locator знаходить, де ця ідентичність лежить.
const reel = useOverlayUrlState({
param: 'reel',
codec: { decode: (raw) => raw, encode: (id) => id },
locator: {
locate: (id) => content.value.findIndex((x) => x.id === id),
identify: (index) => content.value[index].id,
},
});Нескінченні стрічки. locate синхронний, тож відповідає лише за вже завантажені дописи — надіслане посилання на допис 400 у стрічці, де завантажено 20, нічого не знайде. locateAsync — запасний варіант, що викликається лише коли locate misses.
Shortcut
Keying by the item’s id? Не пишіть кодек і локатор вручну — передайте locateAsync просто в urlStableIdKey({ items, locateAsync }) (він вантажить дані, якщо не знайшов, і повертає індекс). Розгорнутий варіант нижче — для адресації за іншим полем або для повного контролю.
const reel = useOverlayUrlState({
param: 'reel',
codec: { decode: (raw) => raw, encode: (id) => id },
locator: {
locate: (id) => content.value.findIndex((x) => x.id === id),
identify: (index) => content.value[index].id,
locateAsync: async (id) => {
const loaded = await loadById(id); // or loadUntil(id) — fetch just that one, or page up to it
if (!loaded) return null; // exhausted — link names no post
content.value = loaded; // commit — the overlay renders from this state
return loaded.findIndex((x) => x.id === id); // wherever it landed
},
},
});- While
locateAsyncу процесі, плеєр лишається закритим, а параметр — недоторканим, тож пряме посилання переживає запит.nullабо відмова прибирає параметр. - Відповідь, що приходить після зміни URL, після закриття або після демонтажу, відкидається — повільний запит не відкриє слайд, якого ніхто не просив.
- Поки триває очікування, нічого не рендериться: цей стан завантаження вже належить сторінці, тож малюйте власний скелетон.
- Тайм-ауту немає — плеєр не може знати, яка стрічка завдовжки. Завершуйте зн аченням
nullколи сторінки скінчилися, інакше оверлей лишиться закритим назавжди.
Довідник API
Пропси ReelPlayerOverlay
ReelPlayerOverlayProps<T>
| Prop | Тип | Типове значення | Опис |
|---|---|---|---|
ariaLabel | string | 'Video player' | Доступна назва області діалогу; екранні читачі оголошують її, коли оверлей відкривається |
aspectRatio | number | 9 / 16 | Співвідношення ширини до висоти контейнера на десктопі. На ширині 768px і вужче плеєр займає всю область перегляду. Нульове, від’ємне чи нескінченне значення замінюється на 9/16. |
content | T[] (extends BaseContentItem) | required | Масив елементів вмісту для показу в плеєрі |
enableNavKeys | boolean | true | Вмикає навігацію стрілками клавіатури |
enableWheel | boolean | true | Вмикає навігацію колесом миші |
initialIndex | number | 0 | Індекс початково видимого елемента, від нуля |
initialInnerIndex | number | 0 | Індекс внутрішнього медіа, з якого відкриватися, лише для початково видимого допису — дає змогу двовісному URL вести просто до потрібного зображення мультимедійного допису. Ігнорується, щойно користувач починає гортати. |
isOpen | boolean | required | Керує видимістю оверлея; якщо false, оверлей прибирається з DOM |
loop | boolean | false | Вмикає нескінченний цикл між слайдами |
swipeDistanceFactor | number | 0.12 | Мінімальна частка відстані свайпу, щоб змінити слайд |
timeline | 'auto' | 'always' | 'never' | 'auto' | Правило показу вбудованої смуги таймлайну. 'auto' показує її лише для відео, довших за timelineMinDurationSeconds; 'always' — щойно на активному слайді є відео; 'never' вимикає вбудовану смугу (для повної заміни беріть слот #timeline). |
timelineMinDurationSeconds | number | 30 | Мінімальна тривалість відео (у секундах), за якої timeline='auto' показує вбудовану смугу. Короткі зациклені кліпи нижче цього порогу не показують її. |
transitionDuration | number | 300 | Тривалість анімації слайда в мілісекундах |
wheelDebounceMs | number | 200 | Тривалість дебаунсу подій колеса в мілісекундах |
Пропси ReelPlayerUrlOverlay
ReelPlayerUrlOverlayProps<T>
Приймає всі пропси вище, крім is-open, replaced by a controller. initial-index ігнорується — слайд обирає position контролера, тож передане поруч значення перезаписується на кожному відкритті.
| Prop | Тип | Типове значення | Опис |
|---|---|---|---|
controller | UrlStateController | required | Контролер із useOverlayUrlState. Its позицією вирішує, чи оверлей відкритий і який слайд показує; оверлей записує через нього назад на зміну слайда та на закриття. |
Events
| Event | Payload | Опис |
|---|---|---|
| @api-ready | ReelPlayerApi | Видається, коли слайдер готовий, і відкриває імперативний API |
| @close | void | Видається, коли плеєр закривається |
| @slide-change | number | Видається з новим індексом активного слайда п ісля зміни |
| @inner-slide-change | outer: number, inner: number | Видається, коли змінюється індекс внутрішнього медіа активного допису — під час внутрішньої навігації та під час активації зовнішнього (поточний внутрішній індекс активованого допису, 0 для допису з одним медіа). |
| @update:is-open | boolean | Видається на закриття; забезпечує роботу `v-model:is-open` |
v-model:is-open
Використовуйте v-model:is-open щоб керувати оверлеєм однією прив’язкою. Давніший патерн :is-open + @close теж працює, якщо вам потрібна явна подія.
<template>
<button @click="open = true">Open</button>
<ReelPlayerOverlay v-model:is-open="open" :content="content" />
</template>Types
ContentItem
| Field | Тип | Опис |
|---|---|---|
id | string | Унікальний ідентифікатор |
media | MediaItem[] | Один або кілька медіафайлів (зображення чи відео) |
author | { name: string; avatar?: string } | Автор, показаний у стандартному оверлеї слайда |
description | string? | Текст підпису |
likes | number? | Кількість лайків |
TimelineBarProps
interface TimelineBarProps {
class?: string;
style?: CSSProperties;
}TimelineSlotScope<T>
interface TimelineSlotScope<T extends BaseContentItem> {
item: T;
activeIndex: number;
timelineState: TimelineController;
defaultContent: () => VNode | VNode[];
}MediaItem
| Field | Тип | Опис |
|---|---|---|
id | string | Унікальний ідентифікатор |
type | 'image' | 'video' | Тип медіа |
src | string | URL медіафайлу |
poster | string? | URL мініатюри-постера для елементів-відео |
aspectRatio | number | співвідношення ширини до висоти. Значення < 1 — вертикальне (cover), ≥ 1 — горизонтальне (contain). |
Об’єднання типів
| Тип | Значення | Опис |
|---|---|---|
MediaType | 'image' | 'video' | Різновид MediaItem; визначає, чи слайд рендерить ImageSlide, чи VideoSlide. |
TimelineMode | 'auto' | 'always' | 'never' | Значення пропса timeline: 'auto' показує смугу для відео, довших за timelineMinDurationSeconds, 'always' — для кожного відео, 'never' її ховає. |
Sub-Components
Вставляйте їх у власні шаблони #controls, #slide, or #slideOverlay . Передавайте розміри й колбеки з області слота, щоб автовідтворення, знімок постера й синхронізація звуку далі працювали.
CloseButton
Окрема кругла кно пка закриття зі стандартними стилями плеєра. Використовуйте всередині #controls.
<script setup lang="ts">
import { CloseButton } from '@reelkit/vue-reel-player';
</script>
<template>
<CloseButton :on-click="onClose" />
<CloseButton :on-click="onClose" class-name="my-close-btn" :style="{ top: '24px', right: '24px' }" />
</template>SoundButton
Перемикач звуку. Рендерте його всередині SoundProvider (ReelPlayerOverlay надає його). Ховається, коли на активному слайді немає відео.
<script setup lang="ts">
import { SoundButton } from '@reelkit/vue-reel-player';
</script>
<template>
<SoundButton />
<SoundButton disabled class-name="my-sound-btn" />
</template>PlayerControls
Стандартні елементи керування, які оверлей рендерить, коли слот #controls не задано: CloseButton, а з show-sound ще й SoundButton. Пропси: showSound, soundDisabled (приглушує кнопку звуку) і onClose. Розмістіть його у власному слоті #controls, щоб зберегти вбудований вигляд поруч із додатковими кнопками.
LoadingIndicator і ErrorIndicator
LoadingIndicator — хвильовий індикатор завантаження на весь слайд, поки медіа буферизується; ErrorIndicator — стандартне повідомлення, коли медіа не вдалося завантажити. Жоден не приймає пропсів. Рендерте їх із власних слотів #loading чи #error, щоб зберегти вбудований вигляд поруч із додатковим вмістом.
TimelineBar
Стандартна смуга перемотування. Читає з найближчого TimelineProvider (автоматично монтується всередині ReelPlayerOverlay) and renders the track, buffered ranges, progress fill, and scrub pill. Theme via the --rk-reel-timeline-* власними властивостями або замініть через слот #timeline slot.
<script setup lang="ts">
import type { TimelineSlotScope } from '@reelkit/vue-reel-player';
</script>
<template>
<!-- Wrap or augment the default bar from #timeline: -->
<ReelPlayerOverlay>
<template #timeline="{ defaultContent }: TimelineSlotScope">
<MyTimecode />
<component :is="defaultContent" />
</template>
</ReelPlayerOverlay>
</template>SlideOverlay
Стандартний градієнтний оверлей з автором, описом і лайками. Показується, коли у вмісті є ці поля. Замінити або сховати його можна через слот #slideOverlay slot.
<script setup lang="ts">
import { SlideOverlay } from '@reelkit/vue-reel-player';
</script>
<template>
<SlideOverlay
:author="{ name: 'John', avatar: '/avatar.jpg' }"
description="Amazing content"
:likes="12500"
/>
</template>ImageSlide
Слайд-зображення з лінивим завантаженням і object-fit: cover за замовчуванням. Складайте його всередині слота #slide щоб змінити рендеринг зображення, зберігши вбудовану поведінку.
<script setup lang="ts">
import { ImageSlide } from '@reelkit/vue-reel-player';
</script>
<template>
<ImageSlide :src="media.src" :size="size" />
<ImageSlide
:src="media.src"
:size="size"
class-name="my-image-slide"
:style="{ backgroundColor: '#1a1a1a', borderRadius: '12px' }"
:img-style="{ objectFit: 'contain' }"
/>
</template>VideoSlide
Слайд-відео на основі спільного елемента <video> . Дає раду безперервності звуку на iOS, постерам і пам’яті позиції. Рендерте його всередині SoundProvider (ReelPlayerOverlay надає його).
<script setup lang="ts">
import { VideoSlide } from '@reelkit/vue-reel-player';
</script>
<template>
<VideoSlide
:src="media.src"
:poster="media.poster"
:aspect-ratio="9 / 16"
:size="size"
:is-active="isActive"
:slide-key="slideKey"
:style="{ borderRadius: '12px' }"
/>
</template>Складання власних слайдів
Використовуйте #slide з ImageSlide / VideoSlide щоб змінити рендеринг медіа, зберігши всю вбудовану поведінку (автовідтворення, знімок постера, синхронізацію звуку).
<script setup lang="ts">
import {
ReelPlayerOverlay,
ImageSlide,
VideoSlide,
} from '@reelkit/vue-reel-player';
</script>
<template>
<ReelPlayerOverlay v-model:is-open="isOpen" :content="content">
<template #slide="{ item, size, isActive, slideKey }">
<ImageSlide
v-if="item.media[0].type === 'image'"
:src="item.media[0].src"
:size="size"
:style="{ backgroundColor: '#111' }"
:img-style="{ objectFit: 'contain' }"
/>
<VideoSlide
v-else
:src="item.media[0].src"
:poster="item.media[0].poster"
:aspect-ratio="item.media[0].aspectRatio"
:size="size"
:is-active="isActive"
:slide-key="slideKey"
:style="{ borderRadius: '16px' }"
/>
</template>
</ReelPlayerOverlay>
</template>Завантаження вмісту та обробка помилок
Плеєр стежить за станом завантаження й помилок кожного слайда. Поки вмі ст вантажиться, показується хвильовий індикатор; для зіпсованого медіа — значок помилки. Плеєр кешує невдалі URL, тож повторне відкриття зіпсованого слайда обходиться без нової спроби.
Колбеки життєвого циклу
Коли використовуєте слот #slide , викликайте ці колбеки з області слота, щоб керувати індикатором завантаження:
| Колбек | Коли викликати |
|---|---|
onReady | Зображення завантажилося або відео почало грати. Скидає стани завантаження й помилки. |
onWaiting | Відео буферизується посеред відтворення. Показує індикатор завантаження. |
onError | Вміст не завантаживс я. Показує оверлей помилки й кешує URL як зіпсований. |
<!-- Inside #slide — wire callbacks to your custom media -->
<template #slide="{ item, size, isActive, onReady, onWaiting, onError }">
<div :style="{ width: size[0] + 'px', height: size[1] + 'px' }">
<img
v-if="item.media[0].type === 'image'"
:src="item.media[0].src"
@load="onReady"
@error="onError"
style="width:100%;height:100%;object-fit:cover"
/>
<video
v-else
:src="item.media[0].src"
:autoplay="isActive"
@canplay="onReady"
@waiting="onWaiting"
@error="onError"
style="width:100%;height:100%;object-fit:cover"
/>
</div>
</template>Власний інтерфейс завантаження та помилок
Замініть стандартний хвильовий індикатор і значок помилки через слоти #loading та #error slots:
<ReelPlayerOverlay v-model:is-open="isOpen" :content="content">
<template #loading="{ activeIndex }">
<div
style="position:absolute;inset:0;z-index:10;display:flex;
align-items:center;justify-content:center;color:#fff;font-size:14px"
>
Loading slide {{ activeIndex + 1 }}...
</div>
</template>
<template #error="{ activeIndex }">
<div
style="position:absolute;inset:0;z-index:10;display:flex;
flex-direction:column;align-items:center;justify-content:center;
gap:12px;color:rgba(255,255,255,0.5)"
>
<span style="font-size:48px">!</span>
<span>Slide {{ activeIndex + 1 }} failed to load</span>
</div>
</template>
</ReelPlayerOverlay>Таймлайн
Оверлей показує вбудовану смугу таймлайну відтворення над активним відео. Керуйте показом через пропс timeline prop: 'auto' (типово) показує її, коли активне медіа — відео, довше за timelineMinDurationSeconds (default 30), 'always' щойно активне відео, 'never' вимикає її. Для повністю власної смуги перемотування б еріть слот #timeline ; його область відкриває timelineState на основі TimelineController.
<ReelPlayerOverlay
:is-open="isOpen"
:content="items"
timeline="auto"
:timeline-min-duration-seconds="30"
@close="isOpen = false"
/>Тему задавайте че рез власні властивості CSS --rk-reel-timeline-* власними властивостями CSS.
Поза слотом той самий контролер читається композаблом:
| Експорт | Опис |
|---|---|
useTimelineState() | Повертає найближчий TimelineController; кидає помилку поза TimelineProvider (оверлей монтує його сам). |
useTimelineStateOptional() | Те саме, що useTimelineState, але поза TimelineProvider повертає null замість помилки — для компонентів, які можуть рендеритися поза оверлеєм. |
RK_TIMELINE_KEY | Ключ ін’єкції, який читають обидва композабли; передайте його через provide самі, щоб керувати TimelineBar власним контролером. |
useViewportSize(aspectRatio) | Реактивний [width, height], за яким оверлей задає свій розмір: задане співвідношення сторін на десктопі, уся область перегляду на ширині 768px і вужче. До монтування повертає [0, 0], тож безпечний під час SSR. |
Контекст звуку
ReelPlayerOverlay монтує SoundProvider у своєму корені, тож будь-який компонент усередині може читати або перемикати стан звуку через useSoundState. Композабл реекспортовано з @reelkit/vue-reel-player тож окремий @reelkit/vue import.
<script setup lang="ts">
import { useSoundState, toVueRef } from '@reelkit/vue';
// Inside a custom control rendered from the #controls slot:
const soundState = useSoundState();
const muted = toVueRef(soundState.muted);
</script>
<template>
<button @click="soundState.toggle()">
{{ muted ? 'Unmute' : 'Mute' }}
</button>
</template>Усере дині плеєра слот #controls також відкриває soundState у своїй області. Беріть саме його, якщо він потрібен лише в шаблоні елементів керування.
Класи CSS
Класи CSS звичайні (не scoped). Таблиця стилів, підключена після @reelkit/vue-reel-player/styles.css може перекрити будь-який із них селектором вищої специфічності. Для змін кольору, розміру та z-index беріть власні властивості CSS із розділу Theming нижче.
| Class | Component | Опис |
|---|---|---|
.rk-reel-overlay | Overlay | Фіксоване повноекранне тло (фон, z-index) |
.rk-reel-container | Overlay | Контейнер плеєра (позиція, переповнення) |
.rk-reel-loader | Overlay | Оверлей хвильової анімації завантаження |
.rk-reel-media-error | Overlay | Оверлей стану помилки (значок і текст по центру) |
.rk-reel-media-error-text | Overlay | Текст повідомлення про помилку |
.rk-reel-button | Controls | Спільна кругла кнопка з іконкою (закриття, звук, стрілки) |
.rk-reel-close-btn | Controls | Кнопка закриття |
.rk-reel-sound-btn | Controls | Кнопка перемикання звуку |
.rk-reel-nav-arrows | Навігація | Контейнер стрілок лише для десктопа (прихований на ширині 768px і вужче) |
.rk-reel-nav-button | Навігація | Окрема стрілка вперед або назад |
.rk-reel-slide-wrapper | Slide | Обгортка навколо медіа та оверлея |
.rk-reel-slide-overlay | SlideOverlay | Контейнер градієнтного оверлея |
.rk-reel-slide-overlay-author | SlideOverlay | Рядок автора (аватар та ім’я) |
.rk-reel-slide-overlay-avatar | SlideOverlay | Зображення аватара авто ра |
.rk-reel-slide-overlay-name | SlideOverlay | Текст імені автора |
.rk-reel-slide-overlay-description | SlideOverlay | Текст опису |
.rk-reel-slide-overlay-likes | SlideOverlay | Рядок лайків (сердечко й лічильник) |
.rk-reel-video-container | VideoSlide | Обгортка відео (фон, переповнення) |
.rk-reel-video-element | VideoSlide | Елемент <video> |
.rk-reel-video-poster | VideoSlide | Постер (зникає з початком відтворення) |
.rk-reel-video-poster.rk-visible | VideoSlide | Модифікатор стану постера, поки відео на паузі або вантажиться |
.rk-reel-nested-indicator | NestedSlider | Пагінація точками під мультимедійними слайдами (позиція різна на десктопі й на дотикових пристроях) |
.rk-reel-nested-nav | NestedSlider | Стрілки горизонтальної каруселі (приховані на ширині 768px і вужче) |
.rk-reel-nested-nav-next | NestedSlider | Позиція вкладеної стрілки вперед |
.rk-reel-nested-nav-prev | NestedSlider | Позиція вкладеної стрілки назад |
.rk-reel-timeline | TimelineBar | Обгортка смуги перемотування. Використовуйте на власних коренях слота `#timeline`, щоб успадкувати притиснення до низу, відступи безпечної зони та проміжок для оверлея слайда на дотикових пристроях. |
.rk-reel-timeline-track | TimelineBar | Доріжка (невідтворена частина) |
.rk-reel-timeline-buffered | TimelineBar | Шар буферизованих сегментів |
.rk-reel-timeline-fill | TimelineBar | Заповнення відтвореного прогресу |
.rk-reel-timeline-cursor | TimelineBar | Повзунок перемотування (плаває над доріжкою) |
Theming
Кожен колір, розмір, z-index і перехід живе у власній властивості CSS. Перевизначайте одну чи к ілька на :root (або на будь-якому предку оверлея), щоб змінити тему, не чіпаючи код компонентів. Токени збігаються з @reelkit/react-reel-player, so overrides port between bindings.
| Token | Типове значення | Controls |
|---|---|---|
--rk-reel-overlay-bg | #000 | Колір повноекранного фону |
--rk-reel-overlay-z | 1000 | z-index оверлею |
--rk-reel-button-bg | rgba(0, 0, 0, 0.5) | Типовий фон круглої кнопки |
--rk-reel-button-bg-hover | rgba(255, 255, 255, 0.1) | Фон стрілки навігації (і базовий стан наведення) |
--rk-reel-button-bg-hover-strong | rgba(255, 255, 255, 0.2) | Фон стрілки навігації під час наведення |
--rk-reel-button-fg | #fff | Колір іконки кнопки |
--rk-reel-button-size | 44px | Ширина й висота кнопки |
--rk-reel-button-radius | 50% | Радіус заокруглення кнопки |
--rk-reel-ui-z | 10 | z-index кнопок закриття, звуку й навігації |
--rk-reel-edge-padding | 16px | Відступ від краю для кнопок закриття, звуку та стрілок навігації |
--rk-reel-nav-gap | 8px | Відстань між розташованими одна над одною стрілками навігації |
--rk-reel-transition | 0.2s | Тривалість переходу під час наведення |
--rk-reel-loader-color | rgba(255, 255, 255, 0.12) | Колір градієнта хвильового завантажувача |
--rk-reel-loader-duration | 1.8s | Тривалість анімації хвильового завантажувача |
--rk-reel-error-fg | rgba(255, 255, 255, 0.4) | Колір іконки й тексту помилки |
--rk-reel-slide-overlay-bg | linear-gradient(transparent, rgba(0, 0, 0, 0.7)) | Градієнт затемнення під підписом |
--rk-reel-slide-overlay-padding | 48px 16px 16px | Внутрішній відступ підпис у |
--rk-reel-slide-overlay-name-color | #fff | Колір імені автора |
--rk-reel-video-bg | #000 | Фон смуг навколо <video> |
--rk-reel-nested-button-bg | rgba(0, 0, 0, 0.5) | Фон вкладеної стрілки |
--rk-reel-nested-button-size | 36px | Розмір вкладеної стрілки |
--rk-reel-timeline-track | rgba(255, 255, 255, 0.22) | Фон доріжки (невідтворена частина) |
--rk-reel-timeline-buffered | rgba(255, 255, 255, 0.4) | Колір буферизованих відрізків |
--rk-reel-timeline-fill | #fff | Колір заповнення відтвореної частини |
--rk-reel-timeline-cursor | #fff | Колір капсули повзунка перемотування |
--rk-reel-timeline-height | 3px | Висота доріжки у спокої |
--rk-reel-timeline-height-active | 6px | Висота доріжки під час наведення, фокусу й перемотування |
--rk-reel-timeline-cursor-width | 10px | Ширина капсули перемотування у спокої |
--rk-reel-timeline-cursor-width-active | 14px | Ширина капсули під час перемотування |
--rk-reel-timeline-cursor-height | 24px | Висота капсули перемотування у спокої |
--rk-reel-timeline-cursor-height-active | 32px | Висота капсули під час перемотування |
--rk-reel-timeline-transition | 0.15s ease-out | Анімація збільшення й зменшення доріжки та капсули |
Вставте фрагмент нижче в таблицю стилів, підключену після @reelkit/vue-reel-player/styles.css.
/* Brand the reel-player overlay */
:root {
--rk-reel-overlay-bg: #0f172a;
--rk-reel-button-bg: rgba(99, 102, 241, 0.65);
--rk-reel-button-bg-hover-strong: rgba(168, 85, 247, 0.85);
--rk-reel-edge-padding: 24px;
--rk-reel-button-size: 52px;
/* Timeline bar: brand-matched, beefier on desktop */
--rk-reel-timeline-track: rgba(99, 102, 241, 0.25);
--rk-reel-timeline-buffered: rgba(168, 85, 247, 0.45);
--rk-reel-timeline-fill: #a855f7;
--rk-reel-timeline-cursor: #a855f7;
--rk-reel-timeline-height: 4px;
--rk-reel-timeline-height-active: 8px;
--rk-reel-timeline-cursor-width-active: 18px;
--rk-reel-timeline-transition: 0.2s ease-out;
}Accessibility
Корінь оверлея — модальний діалог (role="dialog", aria-modal="true"). Set the aria-label щоб змінити оголошення для екранного читача; типове значення — «Video player». Кожен слайд несе role="group", aria-roledescription="slide", та aria-label="Слайд N з M".
Оверлей захоплює фокус під час відкриття й повертає його на елемент-тригер після закриття. Tab і Shift+Tab циклічно проходять фокусовані елементи всередині; фокус, що вислизнув (клік поза оверлеєм, програмна установка), повертається назад. Реалізовано через captureFocusForReturn та createFocusTrap from @reelkit/core.
Клавіатурні скорочення
| Key | Action |
|---|---|
ArrowUp | Попередній слайд |
ArrowDown | Наступний слайд |
ArrowLeft | Попереднє медіа (вкладена карусель) |
ArrowRight | Наступне медіа (вкладена карусель) |
Escape | Закрити плеєр |