Skip to content

Commit 2281b19

Browse files
committed
feat: 书籍支持单页及Auto和双页模式
1 parent 4412697 commit 2281b19

5 files changed

Lines changed: 1027 additions & 229 deletions

File tree

src/components/stateless/InteractiveBook/InteractiveBook.mdx

Lines changed: 118 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as InteractiveBookStories from './InteractiveBook.stories'
99

1010
# InteractiveBook
1111

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

1414
## 组件特性
1515

@@ -18,19 +18,54 @@ import * as InteractiveBookStories from './InteractiveBook.stories'
1818
- 📖 双面内容显示(正面内容 + 背面内容)
1919
- 🌊 页角海浪卷起翻页提示(可配置隐藏)
2020
- 🖱️ 拖拽翻页 & 点击翻页(速度感知阈值)
21+
- 📱 移动端适配:自动检测窄屏切换单页模式,触摸拖拽翻页,安全区适配
22+
- 📄 三种翻页模式:`double`(双页)/ `single`(单页)/ `auto`(自动检测)
23+
- 🎮 受控模式:外部控制书籍开合状态(`open` / `onClose` / `defaultOpen`
2124
- 🎯 空状态处理
2225
- 🔄 页面变化回调
23-
- 📱 响应式设计
2426
- 🖼️ 图片模式(逐页图片渲染,智能懒加载)
2527
- 📄 PDF 模式(react-pdf 渲染,本地 Worker,不依赖 CDN)
2628
- 🔧 可配置导航栏和页角翻页热区
29+
- 🔊 翻页音效(可配置开关和自定义音源)
2730

2831
## 组件预览
2932

3033
<Canvas>
3134
<Story of={InteractiveBookStories.Default} />
3235
</Canvas>
3336

37+
## 单页模式
38+
39+
每次显示一页,适合移动端。点击右侧 60% 翻下一页,左侧 40% 翻上一页,支持滑动手势和角落翻页。
40+
41+
<Canvas>
42+
<Story of={InteractiveBookStories.SinglePageMode} />
43+
</Canvas>
44+
45+
## 单页图片模式
46+
47+
单页模式下每张图片是一个独立页面(双页模式下每 2 张图片组成一个书页正面 + 背面)。
48+
49+
<Canvas>
50+
<Story of={InteractiveBookStories.SinglePageImageBook} />
51+
</Canvas>
52+
53+
## 自动模式(响应式)
54+
55+
`mode="auto"`(默认值):窗口宽度 < 768px 自动切换为单页模式,宽屏使用双页模式。尝试调整浏览器窗口宽度观察效果。
56+
57+
<Canvas>
58+
<Story of={InteractiveBookStories.AutoMode} />
59+
</Canvas>
60+
61+
## 移动端模拟
62+
63+
在 375px 宽容器中模拟移动端效果。书本自适应容器宽度,触摸拖拽翻页,支持 iOS SafeArea 安全区。
64+
65+
<Canvas>
66+
<Story of={InteractiveBookStories.MobileSimulation} />
67+
</Canvas>
68+
3469
## 页角翻页提示
3570

3671
鼠标悬停在页面的右下角或左下角时,页角会出现海浪卷起的动画效果,提示用户可以点击翻页。
@@ -103,6 +138,14 @@ import * as InteractiveBookStories from './InteractiveBook.stories'
103138
<Story of={InteractiveBookStories.SoundDisabled} />
104139
</Canvas>
105140

141+
## 受控模式
142+
143+
使用 `open``onClose` 外部控制书籍开合状态。设置 `defaultOpen={true}` 可直接打开书籍。
144+
145+
<Canvas>
146+
<Story of={InteractiveBookStories.ControlledMode} />
147+
</Canvas>
148+
106149
## Props(可调参数)
107150

108151
<ArgTypes />
@@ -119,6 +162,21 @@ import * as InteractiveBookStories from './InteractiveBook.stories'
119162
| 2 | 图片模式 | 传入 `pageImages`| 逐页加载图片,智能懒加载当前页 ±2 范围 |
120163
| 3 | JSX 模式 | 传入 `pages` | 自定义 React 内容,支持正面/背面 |
121164

165+
## 翻页模式
166+
167+
| 模式 || 说明 |
168+
| --- | --- | --- |
169+
| 双页 | `mode="double"` | 传统书籍展开,左右各一页 |
170+
| 单页 | `mode="single"` | 每次显示一页,适合移动端。点击右侧 60% 翻下一页,左侧 40% 翻上一页 |
171+
| 自动 | `mode="auto"` | 默认值。窗口宽度 < 768px 自动切换单页模式,宽屏使用双页模式 |
172+
173+
### 单页模式交互
174+
175+
- **点击翻页**:点击页面右侧 60% 区域翻下一页,左侧 40% 区域翻上一页
176+
- **拖拽翻页**:向左拖拽翻下一页(右上/右下角折角效果),向右拖拽翻上一页(左上/左下角折角效果)
177+
- **触摸手势**:原生 `touchstart` 事件注册(`passive: false`),确保移动端拖拽不触发浏览器默认滚动
178+
- **容器自适应**:书本自动撑满容器可用宽度(不超过传入的 `width` 上限),保持宽高比
179+
122180
## 基本使用
123181

124182
### JSX 模式(默认)
@@ -196,29 +254,51 @@ function Example() {
196254
}
197255
```
198256

199-
### 带回调的使用
257+
### 单页模式(移动端适配)
200258

201259
```tsx
202260
import InteractiveBook from '@stateless/InteractiveBook'
203261

204262
function Example() {
205-
const handlePageChange = (pageIndex: number) => {
206-
console.log('当前页面索引:', pageIndex)
207-
}
208-
209263
return (
210264
<InteractiveBook
211265
coverImage="https://example.com/cover.jpg"
212-
bookTitle="我的书籍"
213-
bookAuthor="作者名"
266+
bookTitle="移动端阅读"
214267
pages={pages}
215-
onPageChange={handlePageChange}
216-
enableKeyboard={true}
268+
mode="auto" // 窄屏自动切换单页(默认值)
269+
width={350}
270+
height={500}
217271
/>
218272
)
219273
}
220274
```
221275

276+
### 受控模式
277+
278+
```tsx
279+
import InteractiveBook from '@stateless/InteractiveBook'
280+
import { useState } from 'react'
281+
282+
function Example() {
283+
const [isOpen, setIsOpen] = useState(true)
284+
285+
return (
286+
<>
287+
<button onClick={() => setIsOpen(!isOpen)}>
288+
{isOpen ? '关闭' : '打开'}
289+
</button>
290+
<InteractiveBook
291+
coverImage="https://example.com/cover.jpg"
292+
bookTitle="受控书籍"
293+
pages={pages}
294+
open={isOpen}
295+
onClose={() => setIsOpen(false)}
296+
/>
297+
</>
298+
)
299+
}
300+
```
301+
222302
### 配置导航和页角翻页
223303

224304
```tsx
@@ -259,6 +339,13 @@ function Example() {
259339
pages={pages}
260340
enableSound={false}
261341
/>
342+
343+
{/* 自定义音效 */}
344+
<InteractiveBook
345+
coverImage="/cover.jpg"
346+
pages={pages}
347+
soundSrc="/audio/custom-flip.mp3"
348+
/>
262349
</>
263350
)
264351
}
@@ -291,14 +378,16 @@ interface BookPage {
291378

292379
组件支持多种翻页交互方式:
293380

294-
| 交互方式 | 说明 | 可配置 |
295-
| ------------- | --------------------------------------------------- | ---------- |
296-
| 拖拽翻页 | 在页面上按住并左右拖拽,超过阈值后松手翻页 | 始终可用 |
297-
| 页角点击 | 鼠标悬停在页面右下角/左下角,出现卷起效果后点击翻页 | `showCornerFlip` |
298-
| 导航栏按钮 | 底部导航栏的前后翻页按钮 | `showNavigation` |
299-
| 键盘快捷键 | 方向键、Home、End、Escape | `enableKeyboard` |
300-
| 翻页音效 | 翻页时播放纸张翻动音效 | `enableSound` |
301-
| 封面点击/拖拽 | 点击或向左拖拽封面打开书籍 | 始终可用 |
381+
| 交互方式 | 说明 | 可配置 |
382+
| ----------------- | -------------------------------------------------------- | ------------------ |
383+
| 拖拽翻页 | 在页面上按住并左右拖拽,超过阈值后松手翻页 | 始终可用 |
384+
| 页角点击 | 鼠标悬停在页面右下角/左下角,出现卷起效果后点击翻页 | `showCornerFlip` |
385+
| 导航栏按钮 | 底部导航栏的前后翻页按钮 | `showNavigation` |
386+
| 键盘快捷键 | 方向键、Home、End、Escape | `enableKeyboard` |
387+
| 翻页音效 | 翻页时播放纸张翻动音效 | `enableSound` |
388+
| 封面点击/拖拽 | 点击或向左拖拽封面打开书籍 | 始终可用 |
389+
| 点击翻页(单页) | 点击右侧 60% 下一页,左侧 40% 上一页 | 单页模式自动启用 |
390+
| 触摸手势(移动端)| 原生 touch 事件,不触发浏览器默认滚动 | 移动端自动启用 |
302391

303392
> **注意**:最后一页(结束页)禁用拖拽,鼠标光标恢复为普通箭头。
304393
@@ -316,17 +405,16 @@ interface BookPage {
316405
/>
317406
```
318407

319-
### 响应式设计
408+
## 移动端适配
320409

321-
组件支持响应式尺寸设置
410+
组件内置完整的移动端适配
322411

323-
```tsx
324-
<InteractiveBook
325-
width="100%"
326-
height="auto"
327-
// 其他属性...
328-
/>
329-
```
412+
- **自动模式检测**`mode="auto"`(默认)会通过 `matchMedia('(max-width: 768px)')` 检测窄屏,自动切换单页模式
413+
- **容器自适应**:使用 `ResizeObserver` 监测容器宽度,书本自动撑满可用空间(不超过 `width` 上限)
414+
- **触摸事件优化**:使用原生 `addEventListener('touchstart', ..., { passive: false })` 注册,避免 React 合成事件的 passive 限制
415+
- **安全区适配**:底部 padding 使用 `env(safe-area-inset-bottom)` 适配 iOS 刘海/底部安全区
416+
- **极小屏幕**:针对 `<400px` 屏幕(如 iPhone SE)有额外样式压缩
417+
- **滚动隔离**`overscroll-behavior: contain` 防止翻页手势触发父级滚动
330418

331419
## 注意事项
332420

@@ -337,3 +425,5 @@ interface BookPage {
337425
- PDF 模式使用本地 Worker(`pdfjs-dist/build/pdf.worker.min.mjs`),无需外部 CDN
338426
- 图片模式仅预加载当前页 ±2 范围的图片,适合大量页面场景
339427
- `pdfUrl` 优先级高于 `pageImages``pageImages` 优先级高于 `pages`
428+
- 移动端触摸事件使用 `passive: false` 注册,确保 `preventDefault()` 正常工作
429+
- 单页模式下 JSX 内容的 `content``backContent` 会被拆成独立页面

0 commit comments

Comments
 (0)