Stories Player

@reelkit/react-stories-player を使った、React 用の Instagram 風のストーリーのオーバーレイです。

ライブデモを見る →

機能

ネストしたナビゲーション
タップでストーリーを送り、スワイプでグループを切り替え
動画のストーリー
サウンド切り替え付きの自動再生
自動送り
ストーリーごとに設定できるタイマー
3D トランジション
キューブ、フリップ、フェード、ズーム、スライド
進捗バー
canvas で描く分割された進捗
画像と動画
両方のメディアに対応
仮想化
DOM にはスライド 3 枚だけ
ダブルタップでいいね
ダブルタップでハートのアニメーション
デスクトップの操作
デスクトップでは矢印ボタン
ストーリーのリング
Instagram 風のアバターのリング
ジェネリック型
独自のデータで StoryItem を拡張
レンダープロップ
あらゆる UI 要素をカスタマイズ
URL の状態
共有できる ?story=group.story のリンク
既読の状態
既読のリングと続きからの再開がリロード後も残ります

インストール

bash

スタイルのインポートを忘れないでください:

typescript
アイコン

デフォルトのヘッダーはアイコンに lucide-react を使います。別のアイコンライブラリを使いたい場合は、renderHeaderrenderNavigation で独自のものを渡してください。

クイックスタート

StoriesOverlay コンポーネントはフルスクリーンのストーリーのプレイヤーを描画します。Instagram 風の入り口として StoriesRingList と組み合わせてください。StoriesGroup オブジェクトの配列を渡し、isOpen で表示を制御します。

tsx

ライブデモ

StoriesPlayer.tsx
Alice
Alice
Bob
Bob
Charlie
Charlie

ストーリーのリングをクリックするとプレイヤーが開きます。左右をタップして移動し、スワイプでユーザーを切り替えます。

URL の状態

ライブデモを見る →

StoriesUrlOverlay は、開閉状態をアドレスバーに持つ別のコンポーネントです。2 つの軸が 1 つのパラメーター(?story=<group>.<story>)に乗るので、再生中のストーリーに、共有でき、ブックマークでき、戻るボタンで閉じられるリンクが付きます。useOverlayUrlStateurlIndexTwoAxisKey でコントローラーを作り、controller として渡します。

組み込みのキー

ストーリーは二軸なので、二軸のキーをコントローラーに展開します。urlIndexTwoAxisKey(グループとストーリーを位置で指す)か urlStableIdTwoAxisKey(グループを安定した id で指す)で、どちらも @reelkit/react から再エクスポートされています。URL の状態のガイドCore API を参照してください。

tsx
  • 開くと履歴エントリーを 1 つ 積みます。ストーリーのスワイプ ユーザーの切り替えもそれを 置き換え るので、N 回移動してもエントリーは増えず、1 回戻れば必ずプレイヤーが閉じます。戻るとプレイヤーが閉じ、ストーリーは戻りません。
  • 内側の移動も運ばれます。 ストーリーのインデックスはグループ単位で固定されず、ユーザーのストーリー内で進むと ?story=2.n が更新されるので、ディープリンクは正確なストーリーに着きます。
  • 戻るで閉じるのは、アプリ内から開いた場合だけです(リンクがエントリーを積んだ場合)。新しいタブで直接開いた共有リンクには前の履歴がないので、ブラウザーの戻るではサイトを離れます。✕ ボタンか Escape なら、その場でパラメーターを取り除いてページにとどまります。
  • どのグループやストーリーも指さないパラメーター(古いブックマーク、手で書き換えた値、グループの末尾を超えたストーリー)は、隣を開くのではなく URL から取り除かれます。

ルーターを使うアプリでは、アダプターを渡します。 ルーターの裏で history.pushState を書くと、その位置情報が古くなり、次のナビゲーションでパラメーターが消えます:

tsx

安定したリンク。 グループはデフォルトで位置なので、ブックマークした ?story=2.0 はフィードが並べ替えられると別のユーザーを開きます。代わりにグループを安定した id で指してください。outerCodec が id を URL に書き、outerLocator がその場所を探します。ストーリーの側は、解決されたグループ内の単純なインデックスのままです。

tsx

無限フィード。 ページングは codec とは関係なく、outerLocator の役目です。locate は同期なので、読み込み済みのグループにしか答えません。20 件を読み込んだフィードの 400 番目のグループへの共有リンクは見つかりません。locateAsync はフォールバックで、locate が見つけられなかったときだけ呼ばれます。ストーリーは、最終的に決まったグループの範囲に合わせて改めて制限されます。

同じ locateAsync を外側の軸で

これは一軸のキーが受け取るのと同じ locateAsync のページャーです。二軸のキーでは渡した outerLocator に乗るので、グループの軸がページングされ、ストーリーは解決されたグループ内のローカルなインデックスのままです。

tsx
  • locateAsync が保留中のあいだ、プレイヤーは閉じたままで、パラメーターはそのまま残るので、ディープリンクは取得のあいだも失われません。null か reject でパラメーターは取り除かれます。
  • URL が変わったあと、閉じたあと、アンマウントしたあとに届いた答えは破棄されます。遅い取得が、誰も求めていないストーリーを開くことはありません。
  • useOverlayUrlState のすべてのオプションは React API リファレンス に、手順は React ガイド にあります。

見たところを覚えておく

リングは、グループを最後まで見るまでグラデーションを表示し、グループは閲覧者がまだ見ていない最初のストーリーから開きます。どちらも、アドレスバーと同じキーで作った ViewedStateController によるものです。そのため保存されたエントリーは共有リンクのパラメーターとまったく同じになり、キーが id で指すものならフィードが並べ替えられても残ります。

tsx
  • 最初のストーリーも数えます。 画面にすでに表示されているストーリーはマウント時に既読として報告されるので、ストーリーが 1 つだけのグループは開いて閉じれば見たことになります。
  • スワイプで進んでも続きから再開します。 resumeStoryIndex は、このセッションで初めて到達したすべてのグループ(プレイヤーが開いたグループも含む)で参照されます。ただし initialStoryIndex がストーリーを直接指定している場合は別です。すでにスワイプで通過したグループは、離れたところから再び開きます。
  • リンクが優先されます。 StoriesUrlOverlay では、何が保存されていても、プレイヤーがどこで開くかはパラメーターが決めます。それ以外では再開のコールバックが決めます。
  • 件数は合計ではなく位置です。 エントリーが表すのは到達したいちばん先のストーリーなので、見終えたグループにストーリーを追加するとリングがまた点灯し、途中から削除すると件数が減ります。共有リンクと同じ自己修復です。
  • ストレージは差し替えられます。 タブを閉じたら忘れるには createSessionStorageAdapter() を、ほかには独自の StorageAdapter を渡してください。開いている 2 つのタブは、ブラウザーの storage イベントで同期します。

API リファレンス

StoriesOverlayProps

StoriesOverlayProps<T>

Propデフォルト説明
isOpenboolean必須オーバーレイの表示を制御します。true のとき body のスクロールが止まります。
groupsStoriesGroup<T>[]必須表示するストーリーのグループの配列
onClose() => void必須オーバーレイを閉じるコールバック
ariaLabelstring'Stories player'ダイアログ領域のアクセシブルなラベル。オーバーレイが開くとスクリーンリーダーが読み上げます
initialGroupIndexnumber0最初に表示するグループの 0 始まりのインデックス
initialStoryIndexnumberresumeStoryIndex(initialGroupIndex)、なければ 0グループ内で最初に表示するストーリーの 0 始まりのインデックス。指定すると覚えている位置より優先されます。省略すると、開いたグループもほかのグループと同じように続きから始まります。
resumeStoryIndex(groupIndex: number) => numberグループに初めて到達したときに開くストーリー。閲覧者が前回の続きから見られます。今回開いてからまだ訪れていないグループでだけ参照されます。
groupTransitionTransitionTransformFncubeTransition外側(グループ)のスライダーのトランジション効果
defaultImageDurationnumber5000画像のストーリーを自動で送るまでのデフォルトの時間(ミリ秒)
tapZoneSplitnumber0.3タップゾーンの分割比(0–1)。左の部分で前へ、右で次へ移動します。
hideUIOnPausebooleantrue長押しで一時停止したときに、ストーリーの UI(ヘッダー、フッター)を隠すかどうか
enableKeyboardbooleantrueキーボード操作(左右の矢印、Escape)を有効にします
innerTransitionDurationnumber200内側(ストーリー)のトランジションのアニメーションの長さ(ミリ秒)
minSegmentWidthnumber8進捗バーのセグメントの最小幅(ピクセル)
apiRefMutableRefObject<StoriesApi | null>-命令的な StoriesApi にアクセスするための ref
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-独自の読み込み UI のレンダラー。指定しなければヘッダーのデフォルトのスピナーを表示します。
renderError(props: ErrorRenderProps<T>) => ReactNode-独自のエラー UI のレンダラー。指定しなければデフォルトのエラーアイコンのオーバーレイを表示します。

StoriesUrlOverlayProps

StoriesUrlOverlayProps<T>

StoriesOverlay の props をすべて受け取りますが、開閉状態の 3 つ(isOpeninitialGroupIndexinitialStoryIndex)は代わりにコントローラーから与えられます。

Propデフォルト説明
controllerUrlStateController<TwoAxisPosition>必須urlIndexTwoAxisKey を展開した useOverlayUrlState のコントローラー。その位置({ 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プレイヤーが再開したときに呼ばれます

トランジション

groupTransition プロップは、ユーザーのグループ間をスワイプするときの 3D トランジション効果を決めます。トランジション関数は @reelkit/react からインポートします:

tsx

コンテンツ読み込みのライフサイクル

各ストーリーのスライドは、SlideRenderProps を通じて渡されるコールバックで読み込み状態を伝えます:

コールバックタイミング
onReadyコンテンツの準備ができた(画像が読み込まれた、動画が再生中)。進捗のタイマーが始まります。
onWaitingコンテンツが止まった(再生の途中で動画がバッファリング中)。スピナーが表示され、タイマーが止まります。
onErrorコンテンツの読み込みに失敗した。エラーのオーバーレイが表示されます。
onDurationReadyメディアの実際の長さ(たとえば動画のメタデータから)を報告し、正しい長さでタイマーを再開します。
onEndedメディアが終わった(たとえば動画が最後まで再生された)ことを伝えます。次のストーリーへ進みます。
プリローダーのキャッシュ

組み込みの ImageStorySlideVideoStorySlide コンポーネントは、次のストーリーをバックグラウンドで先読みします。先読み済みのストーリーへ移動すると、読み込みのスピナーなしで内容がすぐに表示されます。

レンダープロップ

UI 要素はすべてレンダープロップで置き換えられます。それぞれが、必要な状態とコールバックをすべて持つ型付きの props を受け取ります。

renderHeader

デフォルトのヘッダー(作者の情報、一時停止とミュートのボタン、閉じるボタン)を置き換えます:

tsx

renderFooter

ストーリーの内容の下にフッターを追加します:

tsx

renderSlide

デフォルトの画像と動画のスライドを完全に置き換えます。組み込みのメディア処理には ImageStorySlideVideoStorySlide のサブコンポーネントを使ってください:

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 トークンで調整します(テーマ設定 を参照)。色、大きさ、またはハートを完全に隠すには、.rk-stories-heart クラスを直接指定してください。HeartAnimation コンポーネントも単独で使えるよう公開されています。

css

組み込みのハートのアニメーションは、今のところレンダープロップでは置き換えられません。CSS でスタイルを変えるか、display: none で隠して onDoubleTap コールバックの中で独自のアニメーションを扱ってください。renderDoubleTap のレンダープロップが必要なら、GitHub Issues で知らせてください。

サブコンポーネント

独自のレンダープロップで組み合わせるために公開されている、再利用できる部品です:

CanvasProgressBar

高性能な、canvas で描く分割された進捗バーです。ストーリーごとにセグメントを描き、アクティブなセグメントの塗りを requestAnimationFrame でアニメーションします。ストーリーの多いグループではスライドするウィンドウに対応します。

tsx

StoryHeader

作者のアバター、名前、認証バッジ、相対的な時刻、一時停止と再生の切り替え、ミュートの切り替え、読み込みスピナー、閉じるボタンを持つデフォルトのヘッダーです。renderHeader を指定しない場合に自動で使われます。

tsx

ImageStorySlide

object-fit: cover の、端まで広がる画像スライドです。ライフサイクルの追跡のために、読み込みとエラーをコールバックで報告します。

tsx

VideoStorySlide

iOS で音声を途切れさせないために共有の <video> 要素を使う動画スライドです。自動再生、ポスターのフレーム、サウンドの同期を処理し、長さと再生のライフサイクルのイベントを報告します。

tsx

StoriesRing

Instagram 風のグラデーションのリングが付いた円形のアバターです。状態は 2 つで、まだ見るストーリーが残っているグループは回転するグラデーションに、すべて見たグループは落ち着いた平坦なリングになります。

tsx

StoriesRingList

作者の名前付きの StoriesRing コンポーネントが横に並ぶ、スクロールできる行です。グループごとに 1 つのリングです。

tsx

HeartAnimation

ダブルタップで表示されるハートのアニメーションのオーバーレイです。800ms かけて拡大しながらフェードアウトします。CSS でカスタマイズしてください(ダブルタップといいねの節を参照)。

tsx

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 型

StoryItem を独自のフィールドで拡張し、型パラメーターを StoriesOverlay に渡します。すべてのレンダープロップが拡張した型を受け取ります:

tsx

CSS クラス

CSS クラスはすべて通常のクラス(CSS Modules ではありません)なので、@reelkit/react-stories-player/styles.css のあとに読み込むスタイルシートで、詳細度の高いセレクターを使って指定できます。色、サイズ、z-index を変えるなら、下の テーマ設定 で説明している CSS カスタムプロパティを使ってください。

クラスコンポーネント説明
.rk-stories-overlayOverlay固定配置のフルスクリーンの背景(背景色、z-index)
.rk-stories-swipe-wrapperOverlayスワイプで閉じるためのラッパー(ナビゲーションボタンと canvas を含みます)
.rk-stories-containerOverlay角の丸いストーリーの表示領域(配置、はみ出し)
.rk-stories-ui-layerOverlayUI オーバーレイのコンテナー(ヘッダー、進捗、ナビゲーション)
.rk-stories-ui-layer--hiddenOverlayUI を隠した状態(hideUIOnPause で切り替わります)
.rk-stories-errorOverlayエラー状態(中央のアイコンとテキスト)
.rk-stories-error-textOverlayエラーメッセージのテキスト
.rk-stories-nav-btnNavigationデスクトップの前後の矢印
.rk-stories-progress-barProgressBarcanvas の進捗バーを配置するラッパー
.rk-stories-slide-wrapperGroupストーリーの 1 グループ(外側のスライド)
.rk-stories-storyStory1 つのストーリー(内側のスライドのルート)
.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-elementVideoStorySlide共有の <video> 要素
.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各リングの下の作者の名前

テーマ設定

色、サイズ、z-index、トランジションはすべて CSS カスタムプロパティにあります。:root(またはオーバーレイの任意の祖先)で 1 つでも複数でも上書きすれば、コンポーネントのソースに触れずにテーマを変えられます。

トークンデフォルト制御するもの
--rk-stories-overlay-bg#000フルスクリーンの背景色
--rk-stories-overlay-z9999オーバーレイの z-index
--rk-stories-container-radius12pxストーリーの表示領域の角丸(デスクトップ)
--rk-stories-swipe-gap16pxナビゲーションボタンとストーリーの表示領域の間隔
--rk-stories-top-shade-height120pxヘッダーの後ろの上部のグラデーションの高さ
--rk-stories-top-shade-bglinear-gradient(to bottom, rgba(0,0,0,0.5) 0%, transparent 100%)上部のグラデーションの色
--rk-stories-ui-transition200mshideUIOnPause が切り替わるときのフェードの長さ
--rk-stories-nav-size44pxデスクトップの前後のボタンの大きさ
--rk-stories-nav-bgrgba(255, 255, 255, 0.1)デスクトップのナビゲーションボタンの背景
--rk-stories-nav-bg-hoverrgba(255, 255, 255, 0.2)デスクトップのナビゲーションボタンのホバー時の背景
--rk-stories-nav-fgrgba(255, 255, 255, 0.7)デスクトップのナビゲーションボタンのアイコンの色
--rk-stories-nav-fg-hover#fffデスクトップのナビゲーションボタンのホバー時のアイコンの色
--rk-stories-error-bglinear-gradient(145deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)エラー状態の背景のグラデーション
--rk-stories-error-fgrgba(255, 255, 255, 0.5)エラーアイコンとテキストの色
--rk-stories-error-text-size13pxエラーメッセージの文字サイズ
--rk-stories-video-bg#000<video> の後ろのレターボックスの背景
--rk-stories-video-poster-transition200ms動画の再生が始まるときのポスターのフェードの長さ
--rk-stories-header-top18pxストーリーの上端からのヘッダーの縦の位置
--rk-stories-header-padding12px 16pxヘッダーの行の内側の余白
--rk-stories-header-avatar-size32pxアバターの幅と高さ
--rk-stories-header-name-fg#fff作者の名前の色
--rk-stories-header-name-size14px作者の名前の文字サイズ
--rk-stories-header-time-fgrgba(255, 255, 255, 0.6)「何分前」のテキストの色
--rk-stories-header-btn-fg#fffヘッダーの操作アイコンの色(閉じる、ミュート、一時停止)
--rk-stories-heart-duration800ms飛び出してフェードアウトするアニメーションの長さ
--rk-stories-ring-spin-duration4sまだ見るストーリーが残っているリングの回転の長さ
--rk-stories-ring-list-gap12pxリスト内のリングの間隔
--rk-stories-ring-list-padding12pxリングのリストの内側の余白
--rk-stories-ring-list-name-size12px各リングの下の作者の名前の文字サイズ

下のスニペットを、@reelkit/react-stories-player/styles.css のあとに読み込むスタイルシートに入れてください。

css

アクセシビリティ

オーバーレイのルートはモーダルダイアログ(role="dialog"aria-modal="true")です。ariaLabel を指定すると、スクリーンリーダーの読み上げが変わります。デフォルトは「Stories player」です。

オーバーレイは開くとフォーカスを取り込み、閉じるとトリガーに戻します。Tab と Shift+Tab は中のフォーカス可能な要素を循環し、外へ出たフォーカス(外側のクリック、プログラムからのフォーカス)は引き戻されます。@reelkit/corecaptureFocusForReturncreateFocusTrap で実装されています。

キーボードショートカット

キー動作
ArrowLeft前のストーリー
ArrowRight次のストーリー
Escapeプレイヤーを閉じる