@@ -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
202260import InteractiveBook from ' @stateless/InteractiveBook'
203261
204262function 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