@vysmo/flipbook-react
v0.2.0
Published
React bindings for @vysmo/flipbook — a <Flipbook> component that mounts a real WebGL flipbook with drag-scrub, keyboard nav, and autoplay; useFlipbook hook for imperative control (next/prev/goTo/seek).
Maintainers
Readme
@vysmo/flipbook-react
React bindings for @vysmo/flipbook. Real WebGL flipbook with drag-corner scrub, click halves, keyboard nav, and autoplay — wrapped in one component plus a hook for imperative control.
Install
pnpm add @vysmo/flipbook @vysmo/flipbook-reactreact ≥ 18 is a peer dependency.
Quick start
import { Flipbook } from "@vysmo/flipbook-react";
export function PortfolioBook() {
return (
<Flipbook
pages={[
"/spreads/01.jpg",
"/spreads/02.jpg",
"/spreads/03.jpg",
"/spreads/04.jpg",
]}
style={{ width: 600, height: 800 }}
/>
);
}The component renders a <div>, mounts the flipbook into it, and tears down on unmount. Click halves, arrow keys, and corner drags all work out of the box.
Imperative control
For custom Next/Prev buttons, scroll-bound seek, autoplay toggles, etc., use the hook and call methods on the returned handle:
import { useRef } from "react";
import { useFlipbook } from "@vysmo/flipbook-react";
function ControlledFlipbook({ pages }) {
const ref = useRef<HTMLDivElement>(null);
const flipbook = useFlipbook(ref, { pages, autoplay: { intervalMs: 4000 } });
return (
<>
<div ref={ref} style={{ width: 600, height: 800 }} />
<button onClick={() => flipbook?.prev()}>‹</button>
<button onClick={() => flipbook?.next()}>›</button>
<button onClick={() => flipbook?.isPlaying ? flipbook.pause() : flipbook?.play()}>
Play / Pause
</button>
</>
);
}The handle is null until the flipbook is mounted; use optional chaining or a guard.
Scroll-driven flipping
Pipe scroll progress into seek() for a flipbook that turns pages as the user scrolls:
import { useEffect, useRef } from "react";
import { useFlipbook } from "@vysmo/flipbook-react";
import { createScrollProgress } from "@vysmo/scroll";
function ScrollFlipbook({ pages, section }) {
const ref = useRef<HTMLDivElement>(null);
const flipbook = useFlipbook(ref, { pages });
useEffect(() => {
if (!flipbook) return;
const sub = createScrollProgress({
element: section,
onProgress: (p) => flipbook.seek(p),
});
return () => sub.destroy();
}, [flipbook, section]);
return <div ref={ref} style={{ width: 600, height: 800 }} />;
}Props
| Prop | Type | Default | Notes |
|---|---|---|---|
| pages | readonly PageSource[] | — | URLs (decoded), HTMLImageElements, or canvases. |
| initialPage | number | 0 | Starting index. |
| axis | "horizontal" \| "vertical" | "horizontal" | Curl direction. |
| tilt | number | 0.12 | Hinge tilt in radians on top of the axis baseline. |
| backColor | [number, number, number] | — | Page-back colour. |
| flipDuration | number | 900 | Flip animation ms. |
| ease | (t: number) => number | cubicInOut | Easing for the flip. |
| loop | boolean | false | Wrap last → first. |
| clickNavigation | boolean | true | Click halves to flip. |
| dragNavigation | boolean | true | Drag a corner to peel mid-curl. |
| dragCommitThreshold | number | 0.5 | Released drag past this commits. |
| keyboardNavigation | boolean | true | Arrow keys / Home / End. |
| autoplay | boolean \| { intervalMs } | false | Auto-advance on a timer. |
| ariaLabel | string | "Flipbook" | Accessible label. |
| onChange | (current, previous) => void | — | Page change callback. |
| onFlipStart | (from, to) => void | — | Flip begins. |
| onFlipEnd | (from, to) => void | — | Flip ends. |
| className | string | — | Forwarded to the host <div>. |
| style | CSSProperties | — | Forwarded to the host <div>. Size the flipbook here. |
SSR
The wrapper is SSR-safe: useEffect bodies don't run on the server, and the module body itself doesn't touch window / document. Server renders an empty <div>; client mounts the flipbook.
License
MIT.
