Reel Player
@reelkit/react-reel-player を使った、Instagram Reels/TikTok 風のフルスクリーン動画プレイヤーのコンポーネントです。
機能
インストール
npm install @reelkit/react-reel-player @reelkit/react lucide-reactスタイルのインポートを忘れないでください:
import '@reelkit/react-reel-player/styles.css';アイコン
デフォルトのコントロールはアイコンに lucide-react を使います。別のアイコンライブラリを使いたい場合は、renderControls と renderNavigation で独自のものを渡してください。
クイックスタート
ReelPlayerOverlay コンポーネントはフルスクリーンのプレイヤーを描画します。ContentItem オブジェクトの配列を渡し、isOpen で表示を制御します。
import { useState } from 'react';
import { ReelPlayerOverlay, type ContentItem } from '@reelkit/react-reel-player';
import '@reelkit/react-reel-player/styles.css';
const content: ContentItem[] = [
{
id: '1',
media: [{
id: 'v1',
type: 'video',
src: 'https://example.com/video.mp4',
poster: 'https://example.com/poster.jpg',
aspectRatio: 9 / 16,
}],
author: { name: 'John Doe', avatar: 'https://example.com/avatar.jpg' },
likes: 1234,
description: 'Amazing video!',
},
];
function App() {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<button onClick={() => setIsOpen(true)}>Open Player</button>
<ReelPlayerOverlay
isOpen={isOpen}
onClose={() => setIsOpen(false)}
content={content}
/>
</>
);
}import { useState } from 'react';
import { ReelPlayerOverlay, type ContentItem } from '@reelkit/react-reel-player';
import '@reelkit/react-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-04.mp4',
poster: '/cdn/samples/videos/video-poster-04.jpg',
aspectRatio: 16 / 9,
}],
author: { name: 'James Wilson', avatar: '/cdn/samples/avatars/avatar-03.jpg' },
likes: 3456,
description: 'City life adventures',
},
{
id: '4',
media: [
{ id: 'img2', type: 'image', src: '/cdn/samples/images/image-02.jpg', aspectRatio: 2 / 3 },
{ id: 'img3', type: 'image', src: '/cdn/samples/images/image-03.jpg', aspectRatio: 3 / 4 },
],
author: { name: 'Emma Davis', avatar: '/cdn/samples/avatars/avatar-04.jpg' },
likes: 8901,
description: 'Travel moments',
},
{
id: '5',
media: [{
id: 'img4',
type: 'image',
src: '/cdn/samples/images/image-04.jpg',
aspectRatio: 2 / 3,
}],
author: { name: 'Michael Brown', avatar: '/cdn/samples/avatars/avatar-05.jpg' },
likes: 2345,
description: 'Golden hour magic',
},
{
id: '6',
media: [{
id: 'v3',
type: 'video',
src: '/cdn/samples/videos/video-05.mp4',
poster: '/cdn/samples/videos/video-poster-05.jpg',
aspectRatio: 16 / 9,
}],
author: { name: 'Alex Johnson', avatar: '/cdn/samples/avatars/avatar-01.jpg' },
likes: 7890,
description: 'Living the moment',
},
];
export default function App() {
const [isOpen, setIsOpen] = useState(false);
const [initialIndex, setInitialIndex] = useState(0);
return (
<div style={{ padding: 16, background: '#0f172a', minHeight: '100vh' }}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 6 }}>
{content.map((item, i) => (
<button
key={item.id}
onClick={() => { setInitialIndex(i); setIsOpen(true); }}
style={{
position: 'relative', aspectRatio: '9 / 16', borderRadius: 8,
overflow: 'hidden', border: 'none', padding: 0, cursor: 'pointer',
background: '#1e293b',
}}
>
<img
src={item.media[0].poster || item.media[0].src}
style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
/>
</button>
))}
</div>
<ReelPlayerOverlay
isOpen={isOpen}
onClose={() => setIsOpen(false)}
content={content}
initialIndex={initialIndex}
/>
</div>
);
}サムネイルのグリッドからプレーヤーを開き、見終えた位置に戻すまでの流れはグリッドからプレーヤーへのレシピを参照してください。
カスタマイズ
ジェネリックなコンテンツ型
BaseContentItem を拡張して、独自のデータ型を使います:
import { ReelPlayerOverlay, type BaseContentItem } from '@reelkit/react-reel-player';
interface MyItem extends BaseContentItem {
title: string;
username: string;
}
const items: MyItem[] = [
{
id: '1',
media: [{ id: 'v1', type: 'video', src: '/video.mp4', aspectRatio: 9/16 }],
title: 'My Video',
username: '@user',
},
];
<ReelPlayerOverlay<MyItem>
isOpen={isOpen}
onClose={handleClose}
content={items}
renderSlideOverlay={(item) => (
<div style={{ position: 'absolute', bottom: 16, left: 16, color: '#fff' }}>
<strong>{item.username}</strong>
<p>{item.title}</p>
</div>
)}
/>独自のスライドオーバーレイ
組み込みのスライドオーバーレイを、スライドごとの独自の内容に置き換えます:
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
renderSlideOverlay={(item, index, isActive) => (
<div style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
padding: 16,
background: 'linear-gradient(transparent, rgba(0,0,0,0.8))',
color: '#fff',
}}>
<h3>{item.author.name}</h3>
<p>{item.description}</p>
<span>Slide {index + 1} {isActive ? '(active)' : ''}</span>
</div>
)}
/>メディア以外のスライド
renderSlide で独自の内容(たとえば CTA カード)を差し込みます。デフォルトに戻すには null を返します:
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
renderSlide={({ index, size }) => {
// CTA card on last slide
if (index === content.length - 1) {
return (
<div style={{
width: size[0],
height: size[1],
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'linear-gradient(135deg, #667eea, #764ba2)',
color: '#fff',
}}>
<div style={{ textAlign: 'center' }}>
<h2>Follow for more!</h2>
<button>Subscribe</button>
</div>
</div>
);
}
// Fall back to default MediaSlide + overlay
return null;
}}
/>独自のコントロール
再利用できるサブコンポーネントに、自分の追加要素を組み合わせます:
import {
ReelPlayerOverlay,
CloseButton,
SoundButton,
} from '@reelkit/react-reel-player';
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
renderControls={({ onClose, content, activeIndex }) => (
<>
<CloseButton onClick={onClose} />
<SoundButton />
<button
onClick={() => share(content[activeIndex])}
style={{
position: 'absolute',
bottom: 60,
right: 16,
zIndex: 10,
}}
>
Share
</button>
</>
)}
/>独自のタイムライン
renderTimeline で、組み込みの再生バーを独自のシーク UI に置き換えます。コールバックが呼ばれるのは、オーバーレイの表示条件がデフォルトのバーを描画する場合(同じ timeline モードと timelineMinDurationSeconds の判定)だけなので、それを実装し直す必要はありません。ルート要素で .rk-reel-timeline クラスを使えば、下端に沿った配置、セーフエリアの余白、タッチ端末でのスライドオーバーレイとの間隔を引き継げます。
import { useRef, useEffect } from 'react';
import { ReelPlayerOverlay } from '@reelkit/react-reel-player';
import { Observe } from '@reelkit/react';
function CustomTimelineBar({ timelineState }) {
const trackRef = useRef(null);
useEffect(() => {
if (!trackRef.current) return;
// Pointer + keyboard scrub wiring, same as the built-in bar.
return timelineState.bindInteractions(trackRef.current);
}, [timelineState]);
return (
<div className="rk-reel-timeline" style={{ padding: '0 16px' }}>
<Observe signals={[timelineState.progress, timelineState.currentTime]}>
{() => (
<div
ref={trackRef}
role="slider"
aria-valuenow={timelineState.currentTime.value}
style={{ height: 6, background: 'rgba(255,255,255,0.2)' }}
>
<div style={{
width: `${timelineState.progress.value * 100}%`,
height: '100%',
background: 'linear-gradient(90deg, #6366f1, #ec4899)',
}} />
</div>
)}
</Observe>
</div>
);
}
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
timeline="always"
renderTimeline={({ timelineState }) => (
<CustomTimelineBar timelineState={timelineState} />
)}
/>独自のナビゲーション
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
renderNavigation={({ onPrev, onNext, activeIndex, count }) => (
<div style={{ position: 'absolute', right: 16, top: '50%', transform: 'translateY(-50%)' }}>
<button onClick={onPrev} disabled={activeIndex === 0}>Up</button>
<span>{activeIndex + 1}/{count}</span>
<button onClick={onNext} disabled={activeIndex === count - 1}>Down</button>
</div>
)}
/>独自のネストしたナビゲーション
複数メディアのスライド(横方向のカルーセル)の中にある左右の矢印を、独自のナビゲーションに置き換えます:
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
renderNestedNavigation={({ onPrev, onNext, activeIndex, count }) => (
<div style={{
position: 'absolute',
bottom: 48,
left: 0,
right: 0,
display: 'flex',
justifyContent: 'center',
gap: 8,
zIndex: 10,
}}>
<button onClick={onPrev} disabled={activeIndex === 0}>Prev</button>
<span>{activeIndex + 1} / {count}</span>
<button onClick={onNext} disabled={activeIndex === count - 1}>Next</button>
</div>
)}
/>独自のネストしたスライド
renderNestedSlide で、複数メディアのカルーセル内の 個々のスライドをカスタマイズします。props.defaultContent でデフォルトの ImageSlide/VideoSlide を包むか、完全に置き換えます:
// Wrap default slides with rounded corners
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
renderNestedSlide={({ defaultContent }) => (
<div style={{ borderRadius: 16, overflow: 'hidden' }}>
{defaultContent}
</div>
)}
/>
// Fully custom nested slide for images
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
renderNestedSlide={({ item, size, isActive, slideKey, onVideoRef, defaultContent }) => {
if (item.type === 'video') return defaultContent; // keep default video
return (
<ImageSlide
src={item.src}
size={size}
imgStyle={{ objectFit: 'contain' }}
style={{ backgroundColor: '#111' }}
/>
);
}}
/>URL の状態
ライブデモを見る →ReelPlayerUrlOverlay は、開閉状態をアドレスバーに持つ別のコンポーネントです。@reelkit/react の useOverlayUrlState でコントローラーを作り、controller として渡します。パラメーターがスライドを指すとプレイヤーは自分で開き、パラメーターがなくなると閉じます。リンクは共有でき、戻るボタンはページを離れる代わりにプレイヤーを閉じます。
組み込みのキー
スライドは組み込みのキーで指せます。urlIndexKey(位置で指す)か urlStableIdKey(安定した id で指す)をコントローラーに展開してください。どちらも @reelkit/react から再エクスポートされています。URL の状態のガイド と Core API を参照してください。
import { useOverlayUrlState, urlIndexKey, urlStableIdKey } from '@reelkit/react';
import { ReelPlayerUrlOverlay } from '@reelkit/react-reel-player';
import { Link } from 'react-router-dom';
const reel = useOverlayUrlState({
param: 'reel',
...urlIndexKey(() => content.length),
});
// Opening is a link — the overlay reads the URL and opens itself.
{content.map((item, i) => (
<Link key={item.id} to={`?reel=${i}`}>
<img src={getThumbnail(item)} />
</Link>
))}
<ReelPlayerUrlOverlay controller={reel} content={content} />useOverlayUrlState のすべてのオプション(param、adapter、codec、locator)は React API リファレンス にあります。手順は React ガイド にあります。
- 開くと履歴エントリーを 1 つ 積みます。フィードをスワイプするとそれを 置き換え るので、N 回スワイプしてもエントリーは増えず、1 回戻れば必ずプレイヤーから出られます。戻るとプレイヤーが閉じ、スライドは戻りません。
- 戻るで閉じるのは、アプリ内から開いた場合だけです(リンクがエントリーを積んだ場合)。新しいタブで直接開いた共有リンクには前の履歴がないので、ブラウザーの戻るではサイトを離れます。✕ ボタンか Escape なら、その場でパラメーターを取り除いてページにとどまります。
- ディープリンク
?reel=3は、読み込み時にそのスライドでプレイヤーを開きます。 - どのスライドも指さないパラメーター(古いブックマークや手で書き換えた値)は、開けないスライドをアドレスバーが指 したままにならないよう、URL から取り除かれます。
- デフォルトでは、パラメーターが指すのは 縦方向 の投稿だけです(
?reel=3)。複数メディアのカルーセルの内側のメディアのインデックスも運ぶには、二軸のキーを選んでください。詳しくは下記を参照してください。
キーは 1 つか 2 つか — URL の深さを選ぶ
同じ ReelPlayerUrlOverlay がどちらの形も扱います。実行時にコントローラーの位置から判別するので、モードを指定するプロップはありません。コントローラーを作るときにキーを選んでください:
| キー | 通信形式 | 運ぶもの |
|---|---|---|
urlIndexKey(…) | ?reel=3 | 縦方向の投稿だけ。 |
urlIndexTwoAxisKey(…) | ?reel=3.2 | 投稿 と カルーセルの内側のメディアのインデックス。 |
2 つの通信形式は意図的に区別されています。二軸のキーは厳密にドットで区切る(3.0 で、3 だけにはしない)ので、一軸のリンクが二軸として読まれることはありません。そのため、アプリでキーを切り替えると、それまでに共有されたリンクは無効になります。形を 1 つ選んで使い続けてください。
import { useOverlayUrlState, urlIndexTwoAxisKey } from '@reelkit/react';
const reel = useOverlayUrlState({
param: 'reel',
...urlIndexTwoAxisKey({
outerCount: () => content.length,
innerCounts: () => content.map((post) => post.media.length),
}),
});
// A link now names both axes: post 3, inner media 2.
<Link to="?reel=3.2">…</Link>
<ReelPlayerUrlOverlay controller={reel} content={content} />ルーターを使うアプリでは、アダプターを渡します。 ルーターの裏で history.pushState を書くと、その位置情報が古くなり、次のナビゲーションでパラメーターが消えます:
import { useReactRouterUrlAdapter } from '@reelkit/react/react-router-url-adapter';
const adapter = useReactRouterUrlAdapter();
const reel = useOverlayUrlState({
param: 'reel',
adapter,
...urlIndexKey(() => content.length),
});
<ReelPlayerUrlOverlay controller={reel} content={content} />安定したリンク。 インデックスは位置なので、ブックマークした ?reel=3 はフィードが並べ替えられると別の投稿を開きます。フィードではそれが例外ではなく普通のことです。urlStableIdKey は各投稿の安定した id で指し、現在のフィードを走査します。よくあるケースは 1 回の呼び出しで済みます。
const reel = useOverlayUrlState({
param: 'reel',
...urlStableIdKey({ items: () => content }),
});hashCodec: base64UrlCodec を渡すと、URL の id を base64url でエンコードします。元に戻せる難読化で、暗号学的ハッシュではありません。
別のフィールド(slug)で指したい場合や、locateAsync で無限フィードをページングしたい場合は、codec/locator を自分で作ります。2 つは別の役割です。codec は識別子を URL に書き、locator はその識別子がどこにあるかを探します。
const reel = useOverlayUrlState({
param: 'reel',
codec: { decode: (raw) => raw, encode: (id) => id },
locator: {
locate: (id) => content.findIndex((x) => x.id === id),
identify: (index) => content[index].id,
},
});無限フィード。 locate は同期なので、読み込み済みの投稿にしか答えられません。20 件を読み込んだフィードの 400 番目の投稿への共有リンクは見つかりません。locateAsync はフォール バックで、locate が見つけられなかったときだけ呼ばれます。
近道
アイテムの id で指すなら、codec と locator を手書きする必要はありません。locateAsync をそのまま urlStableIdKey({ items, locateAsync }) に渡してください(見つからなければ取得してからインデックスを返します)。下のより完全な例は、別のフィールドで指す場合や、すべてを自分で制御したい場合のものです。
const reel = useOverlayUrlState({
param: 'reel',
codec: { decode: (raw) => raw, encode: (id) => id },
locator: {
locate: (id) => content.findIndex((x) => x.id === id),
identify: (index) => content[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
setContent(loaded); // commit — the overlay renders from this state
return loaded.findIndex((x) => x.id === id); // wherever it landed
},
},
});locateAsyncが保留中のあいだ、プレイヤーは閉じたままで、パラメーターはそのまま残るので、ディープリンクは取得のあいだも失われません。nullか reject でパラメーターは取り除かれます 。- URL が変わったあと、閉じたあと、アンマウントしたあとに届いた答えは破棄されます。遅い取得が、誰も求めていないスライドを開くことはありません。
- 保留中は何も描画されません。その読み込み状態はすでにページが持っているので、独自のスケルトンを描画してください。
- タイムアウトはありません。プレイヤーにはフィードの長さがわからないからです。ページングが尽きたら
nullで決着をつけてください。そうしないとオーバーレイは閉じたままになります。
API リファレンス
ReelPlayerOverlayProps の Props
ReelPlayerOverlayProps<T>
ReelPlayerOverlayProps は ReelPlayerOverlayBaseProps(両方のオーバーレイが共有する見た目と挙動の props)と ReelPlayerControlledProps(isOpen と onClose)を合わせたものです。どちらのオーバーレイも描画するラッパーの型付けには、ベースの型を使います。
| Prop | 型 | デフォルト | 説明 |
|---|---|---|---|
apiRef | MutableRefObject<ReelApi> | - | Reel の API にアクセスするための ref |
ariaLabel | string | 'Video player' | ダイアログ領域のアクセシブルなラベル。オーバーレイが開くとスクリーンリーダーが読み上げます |
aspectRatio | number | 9/16 (0.5625) | デスクトップでのプレイヤーのコンテナーの幅と高さの比。幅 768px 以下ではプレイヤーは常に全画面を使います。0、負の値、有限でない値は 9/16 になります。 |
content | T[] | 必須 | コンテンツアイテムの配列(ジェネリックで、デフォルトは ContentItem) |
initialIndex | number | 0 | 開始時のスライドのインデックス |
initialInnerIndex | number | 0 | 最初に表示する投稿でだけ使う、内側のメディアのインデックス。二軸の URL で、複数メディアの投稿の特定の画像へ直接リンクできます。プレイヤーが開いてユーザーが移動したあとは無視されます。 |
isOpen | boolean | 必須 | オーバーレイの表示を制御します。URL で開閉を決めたい場合は、別のコンポーネントの ReelPlayerUrlOverlay を使ってください(下の URL の状態を参照)。 |
timeline | 'auto' | 'always' | 'never' | 'auto' | 組み込みの再生タイムラインバーの表示条件。'auto' は timelineMinDurationSeconds より長い動画でだけ表示し、'always' はアクティブなスライドに動画があれば常に表示し、'never' は組み込みのバーを無効にします(完全に置き換えるには renderTimeline を使います)。 |
timelineMinDurationSeconds | number | 30 | timeline='auto' で組み込みのバーを表示する動画の最小の長さ(秒)。これより短いループ動画では表示されません。 |
renderControls | (props: ControlsRenderProps) => ReactNode | - | 独自のコントロール。デ フォルトの閉じるボタンとサウンドボタンを置き換えます |
renderError | (props: { item: T; activeIndex: number }) => ReactNode | - | 独自のエラー表示。デフォルトのエラーアイコンを置き換えます |
renderLoading | (props: { item: T; activeIndex: number }) => ReactNode | - | 独自の読み込み表示。デフォルトの波のローダーを置き換えます |
renderNavigation | (props: NavigationRenderProps) => ReactNode | - | 独自のナビゲーション。デフォルトの縦方向の矢印を置き換えます |
renderNestedNavigation | (props: NavigationRenderProps) => ReactNode | - | ネストした横方向のスライダー(複数メディアの投稿)用の独自のナビゲーション。デフォルトの左右の矢印を置き換えます |
renderNestedSlide | (props: NestedSlideRenderProps) => ReactNode | - | ネストした横方向のスライダーのアイテム用の独自のスライドレンダラー。props.defaultContent でデフォルトの ImageSlide/VideoSlide を包むか埋め込みます。renderSlide と違い、null はデフォルトへのフォールバックとして扱われません。 |
renderSlide | (props: SlideRenderProps) => ReactNode | null | - | 独自のスライド描画。デフォルトに戻すには null を返します。props.defaultContent で デフォルトのスライドを包むか埋め込みます。 |
renderSlideOverlay | (item, index, isActive) => ReactNode | - | スライドごとの独自のオーバーレイ。デフォルトの SlideOverlay を置き換えます。非表示にするには null を返します。 |
renderTimeline | (props: TimelineRenderProps) => ReactNode | - | 独自の再生タイムラインバー。表示条件がデフォルトのバーを描画する場合(同じ auto/always/never と timelineMinDurationSeconds の判定)だけ呼ばれます。props.defaultContent で組み込みの <TimelineBar /> を包み、非表示にするには null を返します。 |
ReelPlayerUrlOverlay の Props
ReelPlayerUrlOverlayProps<T>
上の見た目と挙動の props をすべて受け取りますが、isOpen の代わりに controller を使います。型は ReelPlayerOverlayBaseProps と ReelPlayerUrlControlledProps を合わせたものです。initialIndex は無視されます。スライドはコントローラーの位置で決まるので、一緒に渡した値は開くたびに上書きされます。
| Prop | 型 | デフォルト | 説明 |
|---|---|---|---|
controller | UrlStateController | 必須 | useOverlayUrlState のコントローラー。その位置で、オーバーレイが開いているかどうかと、どのスライドを表示するかが決まります。オーバーレイはスライドの切り替え時と閉じるときに、これを通じて書き戻します。 |
コールバック
| Prop | 型 | 説明 |
|---|---|---|
onClose | () => void | プレイヤーが閉じたときに呼ばれます。ReelPlayerOverlay では必須です(開閉状態を持つのはあ なたなので、閉じる処理も必要です)。ReelPlayerUrlOverlay では任意で、閉じるのは URL が行うので、閉じたあとに反応したいときだけ渡します。 |
onSlideChange | (index: number) => void | スライドが切り替わったあとに呼ばれます |
onInnerSlideChange | (outerIndex: number, innerIndex: number) => void | アクティブな投稿の内側のメディアのインデックスが変わったときに呼ばれます。複数メディアの投稿内で移動したときと、外側でアクティブになったときで、後者はアクティブになった投稿の現在の内側のインデックス(単一メディアの投稿なら 0)を報告します。 |
Reel の Props(引き継ぎ)
これらの props は、内部の Reel コンポーネントに渡されます。型は ReelProxyProps で、オーバーレイが引き継ぐ Reel の props の一部です。それらを転送するラッパーの型付けに使います。
| Prop | 型 | デフォルト | 説明 |
|---|---|---|---|
enableNavKeys | boolean | true | キーボード操作を有効にします |
enableWheel | boolean | true | マウスホイールでの操作を有効にします |
loop | boolean | false | 無限ループを有効にします |
swipeDistanceFactor | number | 0.12 | スワイプのしきい値(0-1) |
transitionDuration | number | 300 | トランジションのアニメーションの長さ(ms) |
wheelDebounceMs | number | 200 | ホイールのデバウンス時間(ms) |
型
BaseContentItem
ジェネリックの制約となる型です。これを拡張すると、ReelPlayerOverlay で独自のデータ型を使えます。
interface BaseContentItem {
id: string;
media: MediaItem[];
}ContentItem
interface ContentItem extends BaseContentItem {
author: {
name: string;
avatar: string;
};
likes: number;
description: string;
}MediaItem
interface MediaItem {
id: string;
type: 'image' | 'video';
src: string;
poster?: string;
aspectRatio: number;
}MediaType
type MediaType = 'image' | 'video';ControlsRenderProps<T>
interface ControlsRenderProps<T extends BaseContentItem> {
onClose: () => void;
soundState: SoundState;
activeIndex: number;
content: T[];
}NavigationRenderProps
interface NavigationRenderProps {
onPrev: () => void;
onNext: () => void;
activeIndex: number;
count: number;
}SlideRenderProps<T>
interface SlideRenderProps<T extends BaseContentItem> {
item: T;
index: number;
size: [number, number];
isActive: boolean;
slideKey: string;
onVideoRef?: (ref: HTMLVideoElement | null) => void;
innerSliderRef: MutableRefObject<ReelApi | null>;
onActiveMediaTypeChange?: (type: 'image' | 'video') => void;
renderNestedNavigation?: (props: NavigationRenderProps) => ReactNode;
enableWheel?: boolean;
defaultContent: ReactNode;
}NestedSlideRenderProps
interface NestedSlideRenderProps {
item: MediaItem;
index: number;
size: [number, number];
isActive: boolean;
isInnerActive: boolean;
slideKey: string;
onVideoRef?: (ref: HTMLVideoElement | null) => void;
defaultContent: ReactNode;
}SlideOverlayProps
interface SlideOverlayProps {
author?: { name: string; avatar: string };
description?: string;
likes?: number;
}ImageSlideProps
interface ImageSlideProps {
src: string;
size: [number, number];
className?: string;
style?: CSSProperties;
imgClassName?: string;
imgStyle?: CSSProperties;
}VideoSlideProps
interface VideoSlideProps {
src: string;
poster?: string;
aspectRatio: number;
size: [number, number];
isActive: boolean;
isInnerActive?: boolean; // default: true
slideKey: string;
onVideoRef?: (ref: HTMLVideoElement | null) => void;
className?: string;
style?: CSSProperties;
}CloseButtonProps
interface CloseButtonProps {
onClick: () => void;
className?: string;
style?: React.CSSProperties;
}SoundButtonProps
interface SoundButtonProps {
disabled?: boolean;
className?: string;
style?: React.CSSProperties;
}TimelineBarProps
interface TimelineBarProps {
className?: string;
style?: React.CSSProperties;
}TimelineRenderProps<T>
interface TimelineRenderProps<T extends BaseContentItem> {
item: T;
activeIndex: number;
timelineState: TimelineController;
defaultContent: ReactNode;
}サブコンポーネント
独自のレンダープロップで組み合わせるために公開されている、再利用できる部品です:
CloseButton
デフォルトのプレイヤーのスタイルを持つ、単独の閉じるボタンです。renderControls の中で使います。
import { CloseButton } from '@reelkit/react-reel-player';
<CloseButton onClick={onClose} />
<CloseButton onClick={onClose} className="my-close-btn" style={{ top: 24, right: 24 }} />SoundButton
単独のサウンド切り替えボタンです。SoundProvider の中に置く必要があります(ReelPlayerOverlay が自動で用意します)。
import { SoundButton } from '@reelkit/react-reel-player';
<SoundButton />
<SoundButton disabled className="my-sound-btn" />TimelineBar
デフォルトの再生シークバーです。いちばん近い TimelineProvider(ReelPlayerOverlay の中に自動でマウントされます)から読み取り、トラック、バッファー済みの範囲、進捗の塗り、シーク用のつまみを描画します。--rk-reel-timeline-* のカスタムプロパティでテーマを変えるか、renderTimeline で置き換えます。
import { TimelineBar } from '@reelkit/react-reel-player';
// Inside renderTimeline — wrap or augment the default bar:
<ReelPlayerOverlay
renderTimeline={({ defaultContent }) => (
<>
<MyTimecode />
{defaultContent}
</>
)}
/>
// Or render standalone inside a custom TimelineProvider tree:
<TimelineBar className="my-timeline" />SlideOverlay
作者、説明、いいねを表示するデフォルトのグラデーションのオーバーレイです。コンテンツに必要なフィールドがあれば自動で描画されます。置き換えるか非表示にするには renderSlideOverlay を使います。
import { SlideOverlay } from '@reelkit/react-reel-player';
<SlideOverlay
author={{ name: 'John', avatar: '/avatar.jpg' }}
description="Amazing content"
likes={12500}
/>ImageSlide
遅延読み込みとデフォルトの object-fit: cover を備えた画像スライドです。renderSlide の中で使い、独自のスタイルの画像スライドを組み立てます。
import { ImageSlide } from '@reelkit/react-reel-player';
// Default usage
<ImageSlide src="/photo.jpg" size={[400, 700]} />
// Custom styles
<ImageSlide
src="/photo.jpg"
size={[400, 700]}
className="my-image-slide"
style={{ backgroundColor: '#1a1a1a', borderRadius: 12 }}
imgStyle={{ objectFit: 'contain' }}
/>VideoSlide
iOS で音声を途切れさせないための共有 <video> 要素、ポスターのフレーム、位置の記憶、読み込み表示を備えた動画スライドです。SoundProvider の中に置く必要があります(ReelPlayerOverlay が自動で用意します)。
import { VideoSlide } from '@reelkit/react-reel-player';
<VideoSlide
src="/video.mp4"
poster="/thumb.jpg"
aspectRatio={9 / 16}
size={[400, 700]}
isActive={true}
slideKey="slide-1"
style={{ borderRadius: 12 }}
/>独自のスライドを組み立てる
renderSlide と ImageSlide/VideoSlide を組み合わせれば、組み込みの挙動(自動再生、ポスターのキャプチャー、サウンドの同期)をすべて保ったまま、メディアの描画をカスタマイズできます。
import {
ReelPlayerOverlay,
ImageSlide,
VideoSlide,
} from '@reelkit/react-reel-player';
<ReelPlayerOverlay
isOpen={isOpen}
onClose={handleClose}
content={content}
renderSlide={({ item, size, isActive, slideKey, onVideoRef }) => {
const media = item.media[0];
if (media.type === 'image') {
return (
<ImageSlide
src={media.src}
size={size}
imgStyle={{ objectFit: 'contain' }}
style={{ backgroundColor: '#111' }}
/>
);
}
if (media.type === 'video') {
return (
<VideoSlide
src={media.src}
poster={media.poster}
aspectRatio={media.aspectRatio}
size={size}
isActive={isActive}
slideKey={slideKey}
onVideoRef={onVideoRef}
style={{ borderRadius: 16 }}
/>
);
}
return null; // fallback to default
}}
/>コンテンツの読み込みとエラー処理
プレイヤーはスライドごとの読み込み中とエラーの状態を追跡します。読み込み中は波のローダーを、壊れたメディアにはエラーアイコンを表示します。エラーになった URL はキャッシュされるので、再訪すると再試行せずにすぐエラーを表示します。
ライフサイクルのコールバック
renderSlide を使う場合は、これらのコールバックを呼んで読み込み表示を制御します:
| コールバック | 呼ぶタイミング |
|---|---|
onReady | 画像が読み込まれたか、動画の再生が始まったとき。読み込み中とエラーの状態をクリアします。 |
onWaiting | 再生の途中で動画がバッファリングしているとき。読み込み表示を出します。 |
onError | コンテンツの読み込みに失敗したとき。エラーのオーバーレイを出し、その URL を壊れたものとしてキャッシュします。 |
// Inside renderSlide — wire callbacks to your custom media
renderSlide={({ item, size, isActive, onReady, onWaiting, onError }) => (
<div style={{ width: size[0], height: size[1] }}>
{item.media[0].type === 'image' ? (
<img
src={item.media[0].src}
onLoad={onReady}
onError={onError}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
) : (
<video
src={item.media[0].src}
autoPlay={isActive}
onCanPlay={onReady}
onWaiting={onWaiting}
onError={onError}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
)}
</div>
)}独自の読み込みとエラーの UI
デフォルトの波のローダーとエラーアイコンを、独自のコンポーネントに置き換えます:
<ReelPlayerOverlay
isOpen={isOpen}
onClose={() => setIsOpen(false)}
content={content}
renderLoading={({ item, activeIndex }) => (
<div style={{
position: 'absolute', inset: 0, zIndex: 10,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#fff', fontSize: 14,
}}>
Loading slide {activeIndex + 1}...
</div>
)}
renderError={({ item, activeIndex }) => (
<div style={{
position: 'absolute', inset: 0, zIndex: 10,
display: 'flex', flexDirection: 'column',
alignItems: 'center', justifyContent: 'center',
gap: 12, color: 'rgba(255,255,255,0.5)',
}}>
<span style={{ fontSize: 48 }}>!</span>
<span>Failed to load media</span>
</div>
)}
/>タイムライン
オーバーレイは、アクティブな動画の上に組み込みの再生タイムラインバーを描画します。TimelineMode 型の timeline プロップで表示条件を決めます:
'auto'(デフォルト):アクティブなメディアがtimelineMinDurationSeconds(デフォルト 30)より長い動画のときに表示します。単一動画のスライドにも複数メディアのカルーセルにも使え、バーはアクティブなネストしたアイテムに追従し、画像では非表示になります。'always':アクティブなスライドに動画があれば常に表示します。'never':表示しません。renderTimelineで独自のバーを作ってください。
<ReelPlayerOverlay
isOpen={isOpen}
onClose={close}
content={items}
timeline="auto"
timelineMinDurationSeconds={30}
/>--rk-reel-timeline-* の CSS カスタムプロパティ(高さ、色、カーソルの大きさ)でテーマを変えます。完全に独自のシークバー、タイムコード、進捗表示を作るには renderTimeline を使います。コールバックは、内部の TimelineController に支えられた timelineState を受け取ります。
オーバーレイの中で描画するコンポーネントは、同じコントローラーを自分で読めます。useTimelineState() は(オーバーレイがマウントする)最も近い TimelineProvider からそれを返し、外側では例外を投げます。useTimelineStateOptional() は外側では代わりに null を返すので、プレイヤーの外でも描画されることがあるコンポーネントに向いています。
サウンドコンテキスト
独自の実装では、サウンドの状態にアクセスできます:
import { SoundProvider, useSoundState } from '@reelkit/react';
// ReelPlayerOverlay wraps itself in a SoundProvider.
// Access sound state inside custom controls:
function CustomControls() {
const soundState = useSoundState();
return (
<button onClick={soundState.toggle}>
{soundState.muted.value ? 'Unmute' : 'Mute'}
</button>
);
}CSS クラス
CSS クラスはすべて通常のクラス(CSS Modules ではありません)なので、@reelkit/react-reel-player/styles.css のあとに読み込むスタイルシートで、詳細度の高いセレクターを使って指定できます。色、サイズ、z-index を変えるなら、下の テ ーマ設定 で説明している CSS カスタムプロパティを使ってください。まさにそのためのものです。
| クラス | コンポーネント | 説明 |
|---|---|---|
.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 | Navigation | デスクトップ専用の矢印のコンテナー(幅 768px 以下では非表示) |
.rk-reel-nav-button | Navigation | 個々の前後のナビゲーション矢印 |
.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 | シークバーのラッパー。独自の `renderTimeline` のルートで使えば、下端に沿った配置、セーフエリアの余白、タッチ端末でのスライドオーバーレイとの間隔を引き継げます。 |
.rk-reel-timeline-track | TimelineBar | トラック(未再生の範囲) |
.rk-reel-timeline-buffered | TimelineBar | バッファー済みの区間のレイヤー |
.rk-reel-timeline-fill | TimelineBar | 再生済みの進捗の塗り |
.rk-reel-timeline-cursor | TimelineBar | シーク用のつまみ(トラックの上に浮かびます) |
テーマ設定
色、サイズ、z-index、トランジションはすべて CSS カスタムプロパティにあり ます。:root(またはオーバーレイの任意の祖先)で 1 つでも複数でも上書きすれば、コンポーネントのソースに触れずにテーマを変えられます。
| トークン | デフォルト | 制御するもの |
|---|---|---|
--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-error-text-size | 13px | エラーメッセージの文字サイズ |
--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-slide-overlay-description-color | rgba(255, 255, 255, 0.9) | 説明のテキストの色 |
--rk-reel-slide-overlay-likes-color | rgba(255, 255, 255, 0.8) | いいねの行のテキストの色 |
--rk-reel-video-bg | #000 | <video> の後ろのレターボックスの背景 |
--rk-reel-nested-button-bg | rgba(0, 0, 0, 0.5) | ネストした矢印の背景 |
--rk-reel-nested-button-bg-hover | rgba(255, 255, 255, 0.2) | ネストした矢印のホバー時の背景 |
--rk-reel-nested-button-size | 36px | ネストした矢印の大きさ |
--rk-reel-nested-edge-padding | 12px | ネストした矢印の端からの距離 |
--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-hitbox | 16px | トラックの上に追加されるポインターの当たり判定 |
--rk-reel-timeline-transition | 0.15s ease-out | トラックとつまみが伸び縮みするアニメーション |
--rk-reel-timeline-z | 11 | タイムラインの z-index(デフォルトの UI レイヤーより上) |
下のスニペットを、@reelkit/react-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;
}アクセシビリティ
オーバーレイのルートはモーダルダイアログ(role="dialog"、aria-modal="true")です。ariaLabel を指定すると、スクリーンリーダーの読み上げが変わります。デフォルトは「Video player」です。各スライドは role="group"、aria-roledescription="slide"、aria-label="Slide N of M" を持つので、スワイプすると並びの中の位置が読み上げられます。
オーバーレイは開くとフォーカスを取り込み、閉じるとトリガーに戻します。Tab と Shift+Tab は中のフォーカス可能な要素を循環し、外へ出たフォーカス(外側のクリック 、プログラムからのフォーカス)は引き戻されます。@reelkit/core の captureFocusForReturn と createFocusTrap で実装されています。
キーボードショートカット
| キー | 動作 |
|---|---|
ArrowUp | 前のスライド |
ArrowDown | 次のスライド |
ArrowLeft | 前のメディア(ネストしたスライダー内) |
ArrowRight | 次のメディア(ネストしたスライダー内) |
Escape | プレイヤーを閉じる |