Stories Player

Оверлей плеєра історій у стилі Instagram для React на основі @reelkit/react-stories-player.

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

Features

Вкладена навігація
Дотик гортає історії, свайп перемикає групи
Історії-відео
Автовідтворення з перемикачем звуку
Auto-Advance
Налаштовний таймер для кожної історії
3D-переходи
Куб, переворот, затухання, масштаб, зсув
Смуга прогресу
Сегментований прогрес на canvas
Зображення та відео
Працює з обома типами медіа
Віртуалізований
Лише 3 слайди в DOM
Лайк подвійним дотиком
Анімація сердечка на подвійний дотик
Навігація на десктопі
Кнопки-стрілки на десктопі
Кільця історій
Кільця аватарів у стилі Instagram
Узагальнені типи
Розширюйте StoryItem власними даними
Render Props
Налаштовуйте кожен елемент інтерфейсу
Стан в URL
Посилання ?story=group.story, якими можна ділитися

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

bash

Не забудьте імпортувати стилі:

typescript
Icons
Стандартна шапка використовує lucide-react для іконок. Якщо ви віддаєте перевагу іншій бібліотеці іконок, скористайтеся renderHeader та renderNavigation , щоб передати власні.

Швидкий старт

The StoriesOverlay рендерить повноекранний плеєр історій. Поєднуйте його з StoriesRingList для точок входу в стилі Instagram. Передайте масив об’єктів StoriesGroup і керуйте видимістю через isOpen.

tsx

Демо наживо

StoriesPlayer.tsx
Alice
Alice
Bob
Bob
Charlie
Charlie

Натисніть кільце історії, щоб відкрити плеєр. Дотик ліворуч або праворуч гортає, свайп перемикає користувачів.

Стан в URL

StoriesUrlOverlay — окремий компонент, стан відкриття якого живе в адресному рядку. Обидві осі їдуть в одному параметрі — ?story=<group>.<story> — тож історія, що грає, має посилання, яким можна поділитися, зберегти в закладки й закрити кнопкою «назад». Побудуйте контролер через useOverlayUrlState та urlIndexTwoAxisKey, а потім передайте його як controller.

Вбудовані клавіші
Історії двовісні, тож розгорніть у контролер двовісний ключ: urlIndexTwoAxisKey (група та історія за позицією) або urlStableIdTwoAxisKey (група за стабільним id) — обидва реекспортовані з @reelkit/react. See the посібник зі стану в URL та API ядра.
tsx
  • Відкриття додає один запис в історію. Свайп історій та і перемикання користувачів replace його, тож N переходів не додають записів, і один крок назад завжди закриває плеєр. «Назад» закриває, а не гортає історії.
  • Внутрішня навігація теж передається. Індекс історії не застигає на рівні групи — перехід між історіями користувача оновлює ?story=2.n тож пряме посилання відкриває саме потрібну історію.
  • «Назад» закриває лише тоді, коли плеєр відкрили всередині застосунку — посилання додало запис. За надісланим посиланням у новій вкладці історії позаду немає, тож кнопка «назад» виведе із сайту; кнопка ✕ або Escape прибирає параметр на місці й лишає вас на сторінці.
  • Параметр, який не називає ані групи, ані історії — застаріла закладка, змінене вручну значення, історія за межами групи — прибирається з URL, а не відкриває сусідню.

Застосунок із роутером — передайте адаптер. Writing history.pushState повз роутер лишає його місцеположення застарілим, і наступна навігація втрачає параметр:

tsx

Стабільні посилання. Група типово адресується за позицією, тож збережений у закладках ?story=2.0 відкриє іншого користувача, щойно стрічку перевпорядкують. Адресуйте групу за стабільним id — outerCodec записує id в URL, outerLocator знаходить, де він лежить. Половина з історією лишається звичайним індексом усередині знайденої групи.

tsx

Нескінченні стрічки. Гортання сторінками — задача outerLocator , незалежна від кодека. locate синхронний, тож відповідає лише за вже завантажені групи — надіслане посилання на групу 400 у стрічці, де завантажено 20, нічого не знайде. locateAsync — запасний варіант, що викликається лише коли locate не знаходить; історія заново обмежується за тією групою, на якій усе зупинилося.

Той самий locateAsync, зовнішня вісь
Це той самий locateAsync пейджер, який приймають одновісні ключі — у двовісному ключі він їде разом із outerLocator , який ви передаєте, тож вісь груп гортається сторінками, а історія лишається локальним індексом усередині знайденої групи.
tsx
  • While locateAsync у процесі, плеєр лишається закритим, а параметр — недоторканим, тож пряме посилання переживає запит. null або відмова прибирає параметр.
  • Відповідь, що приходить після зміни URL, після закриття або після демонтажу, відкидається — повільний запит не відкриє історію, якої ніхто не просив.
  • Full useOverlayUrlState опції — у довіднику API для React, а покроковий розбір — у посібнику для React.

Довідник API

StoriesOverlayProps

StoriesOverlayProps<T>

PropТипТипове значенняОпис
isOpenbooleanrequiredКерує видимістю оверлея. Якщо true, прокручування body заблоковано.
groupsStoriesGroup<T>[]requiredМасив груп історій для показу
onClose() => voidrequiredКолбек для закриття оверлея
ariaLabelstring'Stories player'Доступна назва області діалогу; екранні читачі оголошують її, коли оверлей відкривається
initialGroupIndexnumber0Індекс початково видимої групи, від нуля
initialStoryIndexnumber0Індекс початково видимої історії всередині групи, від нуля
groupTransitionTransitionTransformFncubeTransitionЕфект переходу для зовнішнього слайдера груп
defaultImageDurationnumber5000Типова тривалість автопереходу для історій-зображень у мілісекундах
tapZoneSplitnumber0.3Співвідношення зон дотику (0–1). Ліва частина гортає назад, права — уперед.
hideUIOnPausebooleantrueЧи ховати інтерфейс історії (шапку, підвал) на паузі від довгого натискання
enableKeyboardbooleantrueВмикає навігацію з клавіатури (стрілки ліворуч і праворуч, Escape)
innerTransitionDurationnumber200Тривалість анімації внутрішнього переходу між історіями в мілісекундах
minSegmentWidthnumber8Мінімальна ширина сегмента смуги прогресу в пікселях
apiRefMutableRefObject<StoriesApi | null>-Ref для доступу до імперативного StoriesApi
renderHeader(props: HeaderRenderProps<T>) => ReactNode-Власний рендерер шапки. Отримує автора, історію, стан паузи та звуку.
renderFooter(props: FooterRenderProps<T>) => ReactNode-Власний рендерер підвала. Отримує дані автора та історії.
renderSlide(props: SlideRenderProps<T>) => ReactNode-Власний рендерер слайда, що замінює стандартні слайди із зображенням і відео.
renderNavigation(props: NavigationRenderProps) => ReactNode-Власна навігація для десктопа. Замінює стандартні кнопки-стрілки.
renderProgressBar(props: ProgressBarRenderProps<T>) => ReactNode-Власна смуга прогресу. Замінює стандартну смугу на canvas.
renderLoading(props: LoadingRenderProps<T>) => ReactNode-Власний рендерер стану завантаження. Якщо не задано, показується стандартний індикатор у шапці.
renderError(props: ErrorRenderProps<T>) => ReactNode-Власний рендерер стану помилки. Якщо не задано, показується стандартний значок помилки.

StoriesUrlOverlayProps

StoriesUrlOverlayProps<T>

Приймає всі пропси StoriesOverlay , крім трійки стану відкриття — isOpen, initialGroupIndex, initialStoryIndex , яку натомість дає контролер.

PropТипТипове значенняОпис
controllerUrlStateController<TwoAxisPosition>requiredКонтролер із useOverlayUrlState, розгорнутий разом із urlIndexTwoAxisKey. Його position — об’єкт { outer, inner } — вирішує, чи плеєр відкритий і де саме; оверлей записує назад на кожній навігації та на закритті.

Колбеки

PropТипОпис
onClose() => voidВикликається, коли плеєр закривається. Обов’язковий у StoriesOverlay (стан відкриття ваш, тож закриття обробляєте ви); необов’язковий у StoriesUrlOverlay, де закриттям керує URL — передавайте лише щоб зреагувати після закриття.
onStoryChange(groupIndex: number, storyIndex: number) => voidСпрацьовує, коли змінюється активна історія
onGroupChange(groupIndex: number) => voidСпрацьовує, коли змінюється активна група
onStoryViewed(groupIndex: number, storyIndex: number) => voidСпрацьовує, коли історія стає видимою
onStoryComplete(groupIndex: number, storyIndex: number) => voidСпрацьовує, коли завершується таймер історії
onDoubleTap(groupIndex: number, storyIndex: number) => voidСпрацьовує на подвійний дотик
onPause() => voidСпрацьовує, коли плеєр на паузі
onResume() => voidСпрацьовує, коли плеєр продовжує відтворення

Переходи

The groupTransition керує 3D-ефектом переходу під час свайпу між групами користувачів. Імпортуйте функції переходів із @reelkit/react:

tsx

Життєвий цикл завантаження вмісту

Кожен слайд історії повідомляє свій стан завантаження через колбеки, передані в SlideRenderProps:

КолбекЯкщо
onReadyВміст готовий (зображення завантажено, відео грає). Таймер прогресу запускається.
onWaitingВміст затримується (відео буферизується посеред відтворення). З’являється індикатор, таймер стає на паузу.
onErrorВміст не завантажився. Показується оверлей помилки.
onDurationReadyПовідомте справжню тривалість медіа (наприклад, із метаданих відео), щоб перезапустити таймер із правильною тривалістю.
onEndedСигналізує, що медіа завершилося (наприклад, відео догралося). Відбувається перехід до наступної історії.
Кешування попереднього завантаження
The built-in ImageStorySlide та VideoStorySlide заздалегідь вантажать наступну історію у фоні. Коли користувач переходить до вже завантаженої історії, вміст з’являється миттєво, без індикатора.

Render Props

Будь-який елемент інтерфейсу можна замінити через render props. Кожен отримує типізовані пропси з усім потрібним станом і колбеками.

renderHeader

Замініть стандартну шапку (дані автора, кнопки паузи та звуку, кнопка закриття):

tsx

renderFooter

Додайте підвал під вмістом історії:

tsx

renderSlide

Повністю замініть стандартні слайди із зображенням і відео. Скористайтеся підкомпонентами ImageStorySlide та VideoStorySlide для вбудованої роботи з медіа:

tsx

renderNavigation

Замініть стандартні кнопки-стрілки для десктопа:

tsx

renderProgressBar

Замініть стандартну смугу прогресу на canvas власною реалізацією. Сигнал progress видає значення від 0 до 1:

tsx

renderLoading

Власний індикатор завантаження, поки вміст вантажиться:

tsx

renderError

Власний оверлей помилки, коли вміст не завантажився:

tsx

StoriesApi

Скористайтеся пропсом apiRef для імперативного керування:

tsx

Методи

МетодТипОпис
nextStory()() => voidПерехід до наступної історії в поточній групі
prevStory()() => voidПерехід до попередньої історії в поточній групі
nextGroup()() => voidПеремикає на наступну групу користувача
prevGroup()() => voidПеремикає на попередню групу користувача
goToGroup(index)(index: number) => voidПерехід до конкретної групи за індексом
pause()() => voidПризупиняє автоперехід і таймер прогресу
resume()() => voidВідновлює автоперехід і таймер прогресу

Подвійний дотик і лайки

На подвійний дотик програється вбудована анімація сердечка — миттєвий візуальний відгук. Колбек onDoubleTap спрацьовує з індексами групи та історії, тож ви можете зберегти лайк у власному стані (запит до API, локальне сховище тощо). Сам плеєр стан лайків не веде.

tsx

Налаштування анімації сердечка

Змініть швидкість анімації через --rk-stories-heart-duration token (see Theming). Для кольору, розміру чи повного приховування сердечка звертайтеся до .rk-stories-heart напряму. Компонент HeartAnimation також експортовано для окремого використання.

css
Вбудовану анімацію сердечка поки не можна замінити через render prop. Її можна перестилізувати засобами CSS або сховати через display: none і зробити власну анімацію в колбеку onDoubleTap . Якщо вам потрібен render prop renderDoubleTap , напишіть нам через GitHub Issues.

Sub-Components

Повторно використовувані блоки, експортовані для складання власних render props:

CanvasProgressBar

Швидка сегментована смуга прогресу на canvas. Рендерить сегмент для кожної історії й анімує заповнення активного через requestAnimationFrame. Підтримує рухоме вікно для груп із багатьма історіями.

tsx

StoryHeader

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

tsx

ImageStorySlide

Слайд-зображення на всю площу з object-fit: cover. Повідомляє про завантаження та помилки через колбеки для відстеження життєвого циклу.

tsx

VideoStorySlide

Слайд-відео, що використовує спільний елемент <video> заради безперервності звуку на iOS. Дає раду автовідтворенню, постерам, синхронізації звуку й повідомляє тривалість і події життєвого циклу відтворення.

tsx

StoriesRing

Круглий аватар із градієнтним кільцем у стилі Instagram. Сегменти показують переглянуті й непереглянуті історії — градієнт для непереглянутих, приглушений сірий для переглянутих.

tsx

StoriesRingList

Горизонтальний прокручуваний ряд компонентів StoriesRing з іменами авторів. Одне кільце на групу.

tsx

HeartAnimation

Анімований оверлей сердечка, що спрацьовує на подвійний дотик. Збільшується й згасає за 800 мс. Налаштовується засобами CSS (дивіться розділ про подвійний дотик і лайки).

tsx

Types

StoryItem

typescript

AuthorInfo

typescript

StoriesGroup<T>

typescript

HeaderRenderProps<T>

typescript

FooterRenderProps<T>

typescript

SlideRenderProps<T>

typescript
typescript

ProgressBarRenderProps<T>

typescript

LoadingRenderProps<T>

typescript

ErrorRenderProps<T>

typescript

StoriesApi

typescript

Власні типи Story

Extend StoryItem власними полями й передайте параметр типу в StoriesOverlay. Усі render props отримають ваш розширений тип:

tsx

Класи CSS

Усі класи CSS звичайні (не CSS-модулі), тож їх можна перекрити селекторами вищої специфічності в таблиці стилів, підключеній після @reelkit/react-stories-player/styles.css. Для змін кольору, розміру та z-index краще беріть власні властивості CSS, описані в розділі Theming нижче.

ClassComponentОпис
.rk-stories-overlayOverlayФіксоване повноекранне тло (фон, z-index)
.rk-stories-swipe-wrapperOverlayОбгортка свайпу для закриття (містить кнопки навігації та canvas)
.rk-stories-containerOverlayЗаокруглене полотно історії (позиція, переповнення)
.rk-stories-ui-layerOverlayКонтейнер інтерфейсу (шапка, прогрес, навігація)
.rk-stories-ui-layer--hiddenOverlayСтан прихованого інтерфейсу (перемикається через hideUIOnPause)
.rk-stories-errorOverlayСтан помилки (значок і текст по центру)
.rk-stories-error-textOverlayТекст повідомлення про помилку
.rk-stories-nav-btnНавігаціяСтрілка вперед або назад на десктопі
.rk-stories-progress-barProgressBarОбгортка позиціювання смуги прогресу на canvas
.rk-stories-slide-wrapperGroupОдна група історій (зовнішній слайд)
.rk-stories-storyStoryОдна історія (корінь внутрішнього слайда)
.rk-stories-headerStoryHeaderСмуга шапки (аватар, ім’я, дії)
.rk-stories-header--hiddenStoryHeaderСтан прихованої шапки (visible=false)
.rk-stories-header-avatarStoryHeaderЗображення аватара автора
.rk-stories-header-nameStoryHeaderТекст імені автора
.rk-stories-header-verifiedStoryHeaderКонтейнер значка підтвердження
.rk-stories-header-timeStoryHeaderТекст «скільки часу тому»
.rk-stories-header-actionsStoryHeaderДії праворуч (закрити, вимкнути звук, пауза)
.rk-stories-header-btnStoryHeaderКнопка дії в шапці
.rk-stories-header-spinnerStoryHeaderІндикатор буферизації відео
.rk-stories-imageImageStorySlideЕлемент історії-зображення
.rk-stories-videoVideoStorySlideКонтейнер історії-відео
.rk-stories-video-elementVideoStorySlideThe shared <video> element
.rk-stories-video-posterVideoStorySlideПостер відео (зникає з початком відтворення)
.rk-stories-video-poster--visibleVideoStorySlideСтан видимого постера (до відтворення)
.rk-stories-heartHeartAnimationАнімація сердечка на подвійний дотик
.rk-stories-ringStoriesRingКільце історії (аватар з анімованою градієнтною рамкою)
.rk-stories-ring--activeStoriesRingКільце з непереглянутими історіями (анімується)
.rk-stories-ring-avatarStoriesRingЗображення аватара всередині кільця
.rk-stories-ring-listStoriesRingListКонтейнер горизонтального списку кілець
.rk-stories-ring-list-itemStoriesRingListКолонка з кільцем та іменем
.rk-stories-ring-list-nameStoriesRingListІм’я автора під кожним кільцем

Theming

Кожен колір, розмір, z-index і перехід живе у власній властивості CSS. Перевизначайте одну чи кілька на :root (або на будь-якому предку оверлея), щоб змінити тему, не чіпаючи код компонентів.

TokenТипове значенняControls
--rk-stories-overlay-bg#000Full-screen backdrop color
--rk-stories-overlay-z9999Overlay z-index
--rk-stories-container-radius12pxRounded corners on the story canvas (desktop)
--rk-stories-swipe-gap16pxGap between nav buttons and the story canvas
--rk-stories-top-shade-height120pxTop gradient scrim height behind the header
--rk-stories-top-shade-bglinear-gradient(to bottom, rgba(0,0,0,0.5) 0%, transparent 100%)Top gradient scrim color
--rk-stories-ui-transition200msFade duration when hideUIOnPause toggles
--rk-stories-nav-size44pxDesktop prev/next button size
--rk-stories-nav-bgrgba(255, 255, 255, 0.1)Desktop nav button background
--rk-stories-nav-bg-hoverrgba(255, 255, 255, 0.2)Desktop nav button hover background
--rk-stories-nav-fgrgba(255, 255, 255, 0.7)Desktop nav button icon color
--rk-stories-nav-fg-hover#fffDesktop nav button hover icon color
--rk-stories-error-bglinear-gradient(145deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)Error state background gradient
--rk-stories-error-fgrgba(255, 255, 255, 0.5)Error icon and text color
--rk-stories-error-text-size13pxError message font size
--rk-stories-video-bg#000Letterbox background behind <video>
--rk-stories-video-poster-transition200msPoster fade duration when the video starts playing
--rk-stories-header-top18pxVertical offset of the header from the top of the story
--rk-stories-header-padding12px 16pxInner padding of the header row
--rk-stories-header-avatar-size32pxAvatar width/height
--rk-stories-header-name-fg#fffAuthor name color
--rk-stories-header-name-size14pxAuthor name font size
--rk-stories-header-time-fgrgba(255, 255, 255, 0.6)Time-ago text color
--rk-stories-header-btn-fg#fffHeader action icon color (close, mute, pause)
--rk-stories-heart-duration800msPop-in/fade-out animation duration
--rk-stories-ring-spin-duration4sActive ring gradient rotation duration
--rk-stories-ring-list-gap12pxSpacing between rings in the list
--rk-stories-ring-list-padding12pxInner padding around the ring list
--rk-stories-ring-list-name-size12pxAuthor name font size below each ring

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

css

Accessibility

Корінь оверлея — модальний діалог (role="dialog", aria-modal="true"). Set ariaLabel щоб змінити оголошення для екранного читача; типове значення — «Stories player».

Оверлей захоплює фокус під час відкриття й повертає його на елемент-тригер після закриття. Tab і Shift+Tab циклічно проходять фокусовані елементи всередині; фокус, що вислизнув (клік поза оверлеєм, програмна установка), повертається назад. Реалізовано через captureFocusForReturn та createFocusTrap from @reelkit/core.

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

KeyAction
ArrowLeftPrevious story
ArrowRightNext story
EscapeClose player