Skip to content

Commit 896b1d7

Browse files
committed
feat: interactive book
1 parent 2591077 commit 896b1d7

4 files changed

Lines changed: 283 additions & 96 deletions

File tree

src/components/stateless/InteractiveBook/InteractiveBook.mdx

Lines changed: 149 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ import * as InteractiveBookStories from './InteractiveBook.stories'
99

1010
# InteractiveBook
1111

12-
3D 交互式书籍组件,支持翻页动画、键盘导航和自定义内容。
12+
3D 交互式书籍组件,支持翻页动画、键盘导航和自定义内容。提供 JSX 内容、图片和 PDF 三种渲染模式。
1313

1414
## 组件特性
1515

16-
- 🎨 3D 翻页动画效果
16+
- 🎨 3D 翻页动画效果(基于 Framer Motion)
1717
- ⌨️ 完整的键盘导航支持(箭头键、Escape、Home、End)
1818
- 📖 双面内容显示(正面内容 + 背面内容)
19-
- 页角海浪卷起翻页提示
20-
- 🖱️ 拖拽翻页 & 点击翻页
21-
- 🎯 空状态处理
19+
- 🌊 页角海浪卷起翻页提示(可配置隐藏)
20+
- 🖱️ 拖拽翻页 & 点击翻页(速度感知阈值)
21+
- 🎯 空状态处理
2222
- 🔄 页面变化回调
2323
- 📱 响应式设计
24+
- 🖼️ 图片模式(逐页图片渲染,智能懒加载)
25+
- 📄 PDF 模式(react-pdf 渲染,本地 Worker,不依赖 CDN)
26+
- 🔧 可配置导航栏和页角翻页热区
2427

2528
## 组件预览
2629

@@ -44,15 +47,65 @@ import * as InteractiveBookStories from './InteractiveBook.stories'
4447
<Story of={InteractiveBookStories.FewPages} />
4548
</Canvas>
4649

50+
## 图片模式
51+
52+
传入 `pageImages` 数组后,书页内容改为逐页渲染图片。仅预加载当前页 ±2 范围内的图片,支持几百页不卡顿。
53+
54+
<Canvas>
55+
<Story of={InteractiveBookStories.ImageBook} />
56+
</Canvas>
57+
58+
## PDF 模式
59+
60+
传入 `pdfUrl` 后,使用 react-pdf 渲染 PDF 文件。本地 Worker,不依赖 CDN。每两页 PDF 组成一个书页(正面 + 背面)。
61+
62+
<Canvas>
63+
<Story of={InteractiveBookStories.PdfBook} />
64+
</Canvas>
65+
66+
## 隐藏导航栏
67+
68+
设置 `showNavigation={false}` 可隐藏底部导航栏,仅保留拖拽、页角点击和键盘翻页方式。
69+
70+
<Canvas>
71+
<Story of={InteractiveBookStories.HideNavigation} />
72+
</Canvas>
73+
74+
## 隐藏页角翻页热区
75+
76+
设置 `showCornerFlip={false}` 可隐藏页角海浪翻页热区,仅保留拖拽、导航栏和键盘翻页方式。
77+
78+
<Canvas>
79+
<Story of={InteractiveBookStories.HideCornerFlip} />
80+
</Canvas>
81+
82+
## 极简模式
83+
84+
同时隐藏导航栏和页角翻页热区,仅保留拖拽和键盘翻页,适合嵌入式场景。
85+
86+
<Canvas>
87+
<Story of={InteractiveBookStories.MinimalMode} />
88+
</Canvas>
89+
4790
## Props(可调参数)
4891

4992
<ArgTypes />
5093

5194
<Controls />
5295

96+
## 渲染模式优先级
97+
98+
组件支持三种渲染模式,优先级从高到低:
99+
100+
| 优先级 | 模式 | 触发条件 | 说明 |
101+
| ------ | -------- | ---------------- | ---------------------------------------- |
102+
| 1 | PDF 模式 | 传入 `pdfUrl` | 使用 react-pdf 渲染 PDF 文件 |
103+
| 2 | 图片模式 | 传入 `pageImages`| 逐页加载图片,智能懒加载当前页 ±2 范围 |
104+
| 3 | JSX 模式 | 传入 `pages` | 自定义 React 内容,支持正面/背面 |
105+
53106
## 基本使用
54107

55-
### 简单使用
108+
### JSX 模式(默认)
56109

57110
```tsx
58111
import InteractiveBook from '@stateless/InteractiveBook'
@@ -81,6 +134,52 @@ function Example() {
81134
}
82135
```
83136

137+
### 图片模式
138+
139+
```tsx
140+
import InteractiveBook from '@stateless/InteractiveBook'
141+
142+
// 每张图片对应书的一面,两张组成一个书页(正面 + 背面)
143+
const pageImages = [
144+
'/images/page1.jpg',
145+
'/images/page2.jpg',
146+
'/images/page3.jpg',
147+
// ... 更多图片
148+
]
149+
150+
function Example() {
151+
return (
152+
<InteractiveBook
153+
coverImage="https://example.com/cover.jpg"
154+
bookTitle="图片翻书"
155+
pages={[]}
156+
pageImages={pageImages}
157+
width={350}
158+
height={500}
159+
/>
160+
)
161+
}
162+
```
163+
164+
### PDF 模式
165+
166+
```tsx
167+
import InteractiveBook from '@stateless/InteractiveBook'
168+
169+
function Example() {
170+
return (
171+
<InteractiveBook
172+
coverImage="https://example.com/cover.jpg"
173+
bookTitle="PDF 文档"
174+
pages={[]}
175+
pdfUrl="/assets/pdf/sample.pdf"
176+
width={350}
177+
height={500}
178+
/>
179+
)
180+
}
181+
```
182+
84183
### 带回调的使用
85184

86185
```tsx
@@ -104,6 +203,25 @@ function Example() {
104203
}
105204
```
106205

206+
### 配置导航和页角翻页
207+
208+
```tsx
209+
import InteractiveBook from '@stateless/InteractiveBook'
210+
211+
function Example() {
212+
return (
213+
<InteractiveBook
214+
coverImage="https://example.com/cover.jpg"
215+
bookTitle="极简模式"
216+
pages={pages}
217+
showNavigation={false} // 隐藏底部导航栏
218+
showCornerFlip={false} // 隐藏页角翻页热区
219+
enableKeyboard={true} // 保留键盘翻页
220+
/>
221+
)
222+
}
223+
```
224+
107225
## 页面数据结构
108226

109227
```tsx
@@ -117,44 +235,42 @@ interface BookPage {
117235

118236
## 键盘导航
119237

120-
组件支持以下键盘操作:
238+
组件支持以下键盘操作(需设置 `enableKeyboard={true}`
121239

122-
- `` / ``:前后翻页
123-
- `Escape`:关闭书籍
124-
- `Home`:回到封面
125-
- `End`:跳到最后一页
240+
| 快捷键 | 功能 |
241+
| ---------- | ---------- |
242+
| `` | 向前翻页 |
243+
| `` | 向后翻页 |
244+
| `Escape` | 关闭书籍 |
245+
| `Home` | 回到封面 |
246+
| `End` | 跳到最后页 |
126247

127248
## 交互方式
128249

129250
组件支持多种翻页交互方式:
130251

131-
| 交互方式 | 说明 |
132-
| ------------- | --------------------------------------------------- |
133-
| 拖拽翻页 | 在页面上按住并左右拖拽,超过阈值后松手翻页 |
134-
| 页角点击 | 鼠标悬停在页面右下角/左下角,出现卷起效果后点击翻页 |
135-
| 导航栏按钮 | 底部导航栏的前后翻页按钮 |
136-
| 键盘快捷键 | 方向键、Home、End、Escape |
137-
| 封面点击/拖拽 | 点击或向左拖拽封面打开书籍 |
252+
| 交互方式 | 说明 | 可配置 |
253+
| ------------- | --------------------------------------------------- | ---------- |
254+
| 拖拽翻页 | 在页面上按住并左右拖拽,超过阈值后松手翻页 | 始终可用 |
255+
| 页角点击 | 鼠标悬停在页面右下角/左下角,出现卷起效果后点击翻页 | `showCornerFlip` |
256+
| 导航栏按钮 | 底部导航栏的前后翻页按钮 | `showNavigation` |
257+
| 键盘快捷键 | 方向键、Home、End、Escape | `enableKeyboard` |
258+
| 封面点击/拖拽 | 点击或向左拖拽封面打开书籍 | 始终可用 |
138259

139260
> **注意**:最后一页(结束页)禁用拖拽,鼠标光标恢复为普通箭头。
140261
141262
## 高级用法
142263

143264
### 自定义样式
144265

145-
组件使用 CSS Modules,可以通过以下方式定制样式:
146-
147-
```less
148-
// 自定义样式文件
149-
.my-book {
150-
.container {
151-
// 自定义容器样式
152-
}
266+
组件使用 CSS Modules(Less),可以通过 `className` 传入自定义样式:
153267

154-
.emptyState {
155-
// 自定义空状态样式
156-
}
157-
}
268+
```tsx
269+
<InteractiveBook
270+
className="my-custom-book"
271+
coverImage="/cover.jpg"
272+
pages={pages}
273+
/>
158274
```
159275

160276
### 响应式设计
@@ -175,3 +291,6 @@ interface BookPage {
175291
- 封面图片建议使用合适的尺寸以获得最佳显示效果
176292
- 组件内部使用 Framer Motion 进行动画,性能已优化
177293
- 空页面数组会显示友好的空状态提示
294+
- PDF 模式使用本地 Worker(`pdfjs-dist/build/pdf.worker.min.mjs`),无需外部 CDN
295+
- 图片模式仅预加载当前页 ±2 范围的图片,适合大量页面场景
296+
- `pdfUrl` 优先级高于 `pageImages``pageImages` 优先级高于 `pages`

src/components/stateless/InteractiveBook/InteractiveBook.stories.tsx

Lines changed: 80 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,15 @@ const meta: Meta<typeof InteractiveBook> = {
224224
title: 'Stateless/InteractiveBook',
225225
component: InteractiveBook,
226226
argTypes: {
227-
bookTitle: { control: 'text' },
228-
bookAuthor: { control: 'text' },
229-
width: { control: 'number' },
230-
height: { control: 'number' },
231-
enableKeyboard: { control: 'boolean' },
227+
bookTitle: { control: 'text', description: '书籍标题' },
228+
bookAuthor: { control: 'text', description: '书籍作者' },
229+
width: { control: 'number', description: '书页宽度(px)' },
230+
height: { control: 'number', description: '书页高度(px)' },
231+
enableKeyboard: { control: 'boolean', description: '是否启用键盘导航' },
232+
showNavigation: { control: 'boolean', description: '是否显示底部导航栏' },
233+
showCornerFlip: { control: 'boolean', description: '是否显示页角翻页热区(海浪呼吸效果)' },
234+
pageImages: { control: 'object', description: '图片模式:图片 URL 数组,每张图片对应书的一面' },
235+
pdfUrl: { control: 'text', description: 'PDF 模式:PDF 文件 URL' },
232236
},
233237
parameters: {
234238
layout: 'centered',
@@ -390,3 +394,74 @@ export const PdfBook: Story = {
390394
enableKeyboard: true,
391395
},
392396
}
397+
398+
// ─── 隐藏导航栏 ─────────────────────────────────────
399+
export const HideNavigation: Story = {
400+
name: '隐藏底部导航栏',
401+
render: (args: InteractiveBookProps) => (
402+
<div className="flex h-[800px] w-full flex-col items-center justify-center gap-4 bg-neutral-100 p-10">
403+
<p style={{ color: '#6b7280', fontSize: '0.875rem', textAlign: 'center', maxWidth: 460 }}>
404+
设置 <code>showNavigation=false</code> 隐藏底部导航栏,仅保留拖拽、页角点击和键盘翻页方式。
405+
</p>
406+
<InteractiveBook {...args} />
407+
</div>
408+
),
409+
args: {
410+
coverImage: AiCover,
411+
bookTitle: 'AI Agent 完全指南',
412+
bookAuthor: 'AI 专家',
413+
pages: bookPages,
414+
width: 350,
415+
height: 500,
416+
enableKeyboard: true,
417+
showNavigation: false,
418+
},
419+
}
420+
421+
// ─── 隐藏页角翻页 ─────────────────────────────────────
422+
export const HideCornerFlip: Story = {
423+
name: '隐藏页角翻页热区',
424+
render: (args: InteractiveBookProps) => (
425+
<div className="flex h-[800px] w-full flex-col items-center justify-center gap-4 bg-neutral-100 p-10">
426+
<p style={{ color: '#6b7280', fontSize: '0.875rem', textAlign: 'center', maxWidth: 460 }}>
427+
设置 <code>showCornerFlip=false</code> 隐藏页角海浪翻页热区,仅保留拖拽、导航栏和键盘翻页方式。
428+
</p>
429+
<InteractiveBook {...args} />
430+
</div>
431+
),
432+
args: {
433+
coverImage: AiCover,
434+
bookTitle: 'AI Agent 完全指南',
435+
bookAuthor: 'AI 专家',
436+
pages: bookPages,
437+
width: 350,
438+
height: 500,
439+
enableKeyboard: true,
440+
showCornerFlip: false,
441+
},
442+
}
443+
444+
// ─── 极简模式(同时隐藏导航栏和页角翻页)─────────────
445+
export const MinimalMode: Story = {
446+
name: '极简模式',
447+
render: (args: InteractiveBookProps) => (
448+
<div className="flex h-[800px] w-full flex-col items-center justify-center gap-4 bg-neutral-100 p-10">
449+
<p style={{ color: '#6b7280', fontSize: '0.875rem', textAlign: 'center', maxWidth: 460 }}>
450+
同时设置 <code>showNavigation=false</code><code>showCornerFlip=false</code>
451+
仅保留拖拽和键盘翻页,适合嵌入式场景。
452+
</p>
453+
<InteractiveBook {...args} />
454+
</div>
455+
),
456+
args: {
457+
coverImage: AiCover,
458+
bookTitle: 'AI Agent 完全指南',
459+
bookAuthor: 'AI 专家',
460+
pages: bookPages,
461+
width: 350,
462+
height: 500,
463+
enableKeyboard: true,
464+
showNavigation: false,
465+
showCornerFlip: false,
466+
},
467+
}

src/components/stateless/InteractiveBook/index.module.less

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -586,8 +586,8 @@
586586

587587
.page.dragging {
588588
box-shadow:
589-
0 10px 30px rgb(0 0 0 / 20%),
590-
0 0 0 2px rgb(59 130 246 / 15%);
589+
0 2px 16px rgb(0 0 0 / 14%),
590+
0 0 0 1px rgb(59 130 246 / 10%);
591591

592592
&::before {
593593
content: '';

0 commit comments

Comments
 (0)