Reel Player для Vue

Повноекранний вертикальний медіаплеєр у стилі Instagram чи TikTok для Vue 3 на основі @reelkit/vue-reel-player.

Подивитися демо наживо →

Features

Вертикальний свайп
Дотик, перетягування, клавіатура, колесо
Автовідтворення відео
Грає, коли слайд видимий
Перемикач звуку
Безперервність на iOS
Multi-Media
Вкладені горизонтальні каруселі
Пам’ять позиції
Продовжує з місця, де зупинилися
Знімок кадру
Плавний перехід від постера до відео
Віртуалізований
Лише 3 слайди в DOM
Співвідношення сторін
9:16 на десктопі, на весь екран на мобільних
Навігація на десктопі
Кнопки-стрілки
Узагальнені типи
Власні моделі даних вмісту
Слоти з областю видимості
Налаштовуйте кожен елемент інтерфейсу
v-model:is-open
Двостороння прив’язка видимості
Стан в URL
Посилання, якими можна ділитися, і закриття кнопкою «назад»

Встановлення

bash
npm install @reelkit/vue-reel-player @reelkit/vue lucide-vue-next

Імпортуйте таблицю стилів один раз у точці входу застосунку (або в будь-якому компоненті):

typescript
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.

App.vue
<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>

Слоти з областю видимості

Вісім слотів з областю видимості дають замінити будь-яку частину інтерфейсу плеєра. Кожен отримує строго типізований об’єкт області. Слоти, які ви не передали, лишаються стандартними.

SlotScopeОпис
#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 />.
vue
<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 на своєму корені, щоб успадкувати притиснення до низу, відступи безпечної зони та проміжок на дотикових пристроях.

vue
<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, тож тримайте їх узгодженими:

vue
<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.

vue
<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 контролера, тож жодного пропса режиму немає. Ключ обирайте, коли будуєте контролер:

KeyWireCarries
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.

typescript
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.

typescript
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 знаходить, де ця ідентичність лежить.

typescript
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 }) (він вантажить дані, якщо не знайшов, і повертає індекс). Розгорнутий варіант нижче — для адресації за іншим полем або для повного контролю.

typescript
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ТипТипове значенняОпис
ariaLabelstring'Video player'Доступна назва області діалогу; екранні читачі оголошують її, коли оверлей відкривається
aspectRationumber9 / 16Співвідношення ширини до висоти контейнера на десктопі. На ширині 768px і вужче плеєр займає всю область перегляду. Нульове, від’ємне чи нескінченне значення замінюється на 9/16.
contentT[] (extends BaseContentItem)requiredМасив елементів вмісту для показу в плеєрі
enableNavKeysbooleantrueВмикає навігацію стрілками клавіатури
enableWheelbooleantrueВмикає навігацію колесом миші
initialIndexnumber0Індекс початково видимого елемента, від нуля
initialInnerIndexnumber0Індекс внутрішнього медіа, з якого відкриватися, лише для початково видимого допису — дає змогу двовісному URL вести просто до потрібного зображення мультимедійного допису. Ігнорується, щойно користувач починає гортати.
isOpenbooleanrequiredКерує видимістю оверлея; якщо false, оверлей прибирається з DOM
loopbooleanfalseВмикає нескінченний цикл між слайдами
swipeDistanceFactornumber0.12Мінімальна частка відстані свайпу, щоб змінити слайд
timeline'auto' | 'always' | 'never''auto'Правило показу вбудованої смуги таймлайну. 'auto' показує її лише для відео, довших за timelineMinDurationSeconds; 'always' — щойно на активному слайді є відео; 'never' вимикає вбудовану смугу (для повної заміни беріть слот #timeline).
timelineMinDurationSecondsnumber30Мінімальна тривалість відео (у секундах), за якої timeline='auto' показує вбудовану смугу. Короткі зациклені кліпи нижче цього порогу не показують її.
transitionDurationnumber300Тривалість анімації слайда в мілісекундах
wheelDebounceMsnumber200Тривалість дебаунсу подій колеса в мілісекундах

Пропси ReelPlayerUrlOverlay

ReelPlayerUrlOverlayProps<T>

Приймає всі пропси вище, крім is-open, replaced by a controller. initial-index ігнорується — слайд обирає position контролера, тож передане поруч значення перезаписується на кожному відкритті.

PropТипТипове значенняОпис
controllerUrlStateControllerrequiredКонтролер із useOverlayUrlState. Its позицією вирішує, чи оверлей відкритий і який слайд показує; оверлей записує через нього назад на зміну слайда та на закриття.

Events

EventPayloadОпис
@api-readyReelPlayerApiВидається, коли слайдер готовий, і відкриває імперативний API
@closevoidВидається, коли плеєр закривається
@slide-changenumberВидається з новим індексом активного слайда після зміни
@inner-slide-changeouter: number, inner: numberВидається, коли змінюється індекс внутрішнього медіа активного допису — під час внутрішньої навігації та під час активації зовнішнього (поточний внутрішній індекс активованого допису, 0 для допису з одним медіа).
@update:is-openbooleanВидається на закриття; забезпечує роботу `v-model:is-open`

v-model:is-open

Використовуйте v-model:is-open щоб керувати оверлеєм однією прив’язкою. Давніший патерн :is-open + @close теж працює, якщо вам потрібна явна подія.

vue
<template>
  <button @click="open = true">Open</button>
  <ReelPlayerOverlay v-model:is-open="open" :content="content" />
</template>

Types

ContentItem

FieldТипОпис
idstringУнікальний ідентифікатор
mediaMediaItem[]Один або кілька медіафайлів (зображення чи відео)
author{ name: string; avatar?: string }Автор, показаний у стандартному оверлеї слайда
descriptionstring?Текст підпису
likesnumber?Кількість лайків

TimelineBarProps

typescript
interface TimelineBarProps {
  class?: string;
  style?: CSSProperties;
}

TimelineSlotScope<T>

typescript
interface TimelineSlotScope<T extends BaseContentItem> {
  item: T;
  activeIndex: number;
  timelineState: TimelineController;
  defaultContent: () => VNode | VNode[];
}

MediaItem

FieldТипОпис
idstringУнікальний ідентифікатор
type'image' | 'video'Тип медіа
srcstringURL медіафайлу
posterstring?URL мініатюри-постера для елементів-відео
aspectRationumberспіввідношення ширини до висоти. Значення < 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.

vue
<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 надає його). Ховається, коли на активному слайді немає відео.

vue
<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.

vue
<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.

vue
<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 щоб змінити рендеринг зображення, зберігши вбудовану поведінку.

vue
<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 надає його).

vue
<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 щоб змінити рендеринг медіа, зберігши всю вбудовану поведінку (автовідтворення, знімок постера, синхронізацію звуку).

vue
<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 як зіпсований.
vue
<!-- 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:

vue
<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.

vue
<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.

vue
<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 нижче.

ClassComponentОпис
.rk-reel-overlayOverlayФіксоване повноекранне тло (фон, z-index)
.rk-reel-containerOverlayКонтейнер плеєра (позиція, переповнення)
.rk-reel-loaderOverlayОверлей хвильової анімації завантаження
.rk-reel-media-errorOverlayОверлей стану помилки (значок і текст по центру)
.rk-reel-media-error-textOverlayТекст повідомлення про помилку
.rk-reel-buttonControlsСпільна кругла кнопка з іконкою (закриття, звук, стрілки)
.rk-reel-close-btnControlsКнопка закриття
.rk-reel-sound-btnControlsКнопка перемикання звуку
.rk-reel-nav-arrowsНавігаціяКонтейнер стрілок лише для десктопа (прихований на ширині 768px і вужче)
.rk-reel-nav-buttonНавігаціяОкрема стрілка вперед або назад
.rk-reel-slide-wrapperSlideОбгортка навколо медіа та оверлея
.rk-reel-slide-overlaySlideOverlayКонтейнер градієнтного оверлея
.rk-reel-slide-overlay-authorSlideOverlayРядок автора (аватар та ім’я)
.rk-reel-slide-overlay-avatarSlideOverlayЗображення аватара автора
.rk-reel-slide-overlay-nameSlideOverlayТекст імені автора
.rk-reel-slide-overlay-descriptionSlideOverlayТекст опису
.rk-reel-slide-overlay-likesSlideOverlayРядок лайків (сердечко й лічильник)
.rk-reel-video-containerVideoSlideОбгортка відео (фон, переповнення)
.rk-reel-video-elementVideoSlideЕлемент <video>
.rk-reel-video-posterVideoSlideПостер (зникає з початком відтворення)
.rk-reel-video-poster.rk-visibleVideoSlideМодифікатор стану постера, поки відео на паузі або вантажиться
.rk-reel-nested-indicatorNestedSliderПагінація точками під мультимедійними слайдами (позиція різна на десктопі й на дотикових пристроях)
.rk-reel-nested-navNestedSliderСтрілки горизонтальної каруселі (приховані на ширині 768px і вужче)
.rk-reel-nested-nav-nextNestedSliderПозиція вкладеної стрілки вперед
.rk-reel-nested-nav-prevNestedSliderПозиція вкладеної стрілки назад
.rk-reel-timelineTimelineBarОбгортка смуги перемотування. Використовуйте на власних коренях слота `#timeline`, щоб успадкувати притиснення до низу, відступи безпечної зони та проміжок для оверлея слайда на дотикових пристроях.
.rk-reel-timeline-trackTimelineBarДоріжка (невідтворена частина)
.rk-reel-timeline-bufferedTimelineBarШар буферизованих сегментів
.rk-reel-timeline-fillTimelineBarЗаповнення відтвореного прогресу
.rk-reel-timeline-cursorTimelineBarПовзунок перемотування (плаває над доріжкою)

Theming

Кожен колір, розмір, z-index і перехід живе у власній властивості CSS. Перевизначайте одну чи кілька на :root (або на будь-якому предку оверлея), щоб змінити тему, не чіпаючи код компонентів. Токени збігаються з @reelkit/react-reel-player, so overrides port between bindings.

TokenТипове значенняControls
--rk-reel-overlay-bg#000Колір повноекранного фону
--rk-reel-overlay-z1000z-index оверлею
--rk-reel-button-bgrgba(0, 0, 0, 0.5)Типовий фон круглої кнопки
--rk-reel-button-bg-hoverrgba(255, 255, 255, 0.1)Фон стрілки навігації (і базовий стан наведення)
--rk-reel-button-bg-hover-strongrgba(255, 255, 255, 0.2)Фон стрілки навігації під час наведення
--rk-reel-button-fg#fffКолір іконки кнопки
--rk-reel-button-size44pxШирина й висота кнопки
--rk-reel-button-radius50%Радіус заокруглення кнопки
--rk-reel-ui-z10z-index кнопок закриття, звуку й навігації
--rk-reel-edge-padding16pxВідступ від краю для кнопок закриття, звуку та стрілок навігації
--rk-reel-nav-gap8pxВідстань між розташованими одна над одною стрілками навігації
--rk-reel-transition0.2sТривалість переходу під час наведення
--rk-reel-loader-colorrgba(255, 255, 255, 0.12)Колір градієнта хвильового завантажувача
--rk-reel-loader-duration1.8sТривалість анімації хвильового завантажувача
--rk-reel-error-fgrgba(255, 255, 255, 0.4)Колір іконки й тексту помилки
--rk-reel-slide-overlay-bglinear-gradient(transparent, rgba(0, 0, 0, 0.7))Градієнт затемнення під підписом
--rk-reel-slide-overlay-padding48px 16px 16pxВнутрішній відступ підпису
--rk-reel-slide-overlay-name-color#fffКолір імені автора
--rk-reel-video-bg#000Фон смуг навколо <video>
--rk-reel-nested-button-bgrgba(0, 0, 0, 0.5)Фон вкладеної стрілки
--rk-reel-nested-button-size36pxРозмір вкладеної стрілки
--rk-reel-timeline-trackrgba(255, 255, 255, 0.22)Фон доріжки (невідтворена частина)
--rk-reel-timeline-bufferedrgba(255, 255, 255, 0.4)Колір буферизованих відрізків
--rk-reel-timeline-fill#fffКолір заповнення відтвореної частини
--rk-reel-timeline-cursor#fffКолір капсули повзунка перемотування
--rk-reel-timeline-height3pxВисота доріжки у спокої
--rk-reel-timeline-height-active6pxВисота доріжки під час наведення, фокусу й перемотування
--rk-reel-timeline-cursor-width10pxШирина капсули перемотування у спокої
--rk-reel-timeline-cursor-width-active14pxШирина капсули під час перемотування
--rk-reel-timeline-cursor-height24pxВисота капсули перемотування у спокої
--rk-reel-timeline-cursor-height-active32pxВисота капсули під час перемотування
--rk-reel-timeline-transition0.15s ease-outАнімація збільшення й зменшення доріжки та капсули

Вставте фрагмент нижче в таблицю стилів, підключену після @reelkit/vue-reel-player/styles.css.

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.

Клавіатурні скорочення

KeyAction
ArrowUpПопередній слайд
ArrowDownНаступний слайд
ArrowLeftПопереднє медіа (вкладена карусель)
ArrowRightНаступне медіа (вкладена карусель)
EscapeЗакрити плеєр