@agilehub/vue-pdf-flipbook 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # Vue PDF Flipbook Next
2
+
3
+ 基于 Vue 3、PDF.js 和 PageFlip 的 PDF 翻页组件。核心只负责 PDF 加载、页面渲染、单页 / 双页布局和点击、拖拽翻页,内置阅读区两侧翻页按钮和可选的底部缩略图列表,不包含工具栏、缩放、全屏、快捷键或加载提示 UI,也不依赖 UI 框架。
4
+
5
+ ## 安装与使用
6
+
7
+ ```bash
8
+ npm install vue-pdf-flipbook-next
9
+ ```
10
+
11
+ ```vue
12
+ <script setup lang="ts">
13
+ import { ref } from 'vue'
14
+ import { VuePdfFlipbook } from 'vue-pdf-flipbook-next'
15
+ import type { PdfFlipbookExpose, PdfFlipbookState } from 'vue-pdf-flipbook-next'
16
+ import 'vue-pdf-flipbook-next/style.css'
17
+
18
+ const reader = ref<PdfFlipbookExpose>()
19
+ const state = ref<PdfFlipbookState>()
20
+ </script>
21
+
22
+ <template>
23
+ <VuePdfFlipbook
24
+ ref="reader"
25
+ url="https://example.com/catalog.pdf"
26
+ :height="720"
27
+ @state-change="state = $event"
28
+ />
29
+ <p v-if="state?.loading">加载中 {{ state.progress }}%</p>
30
+ <p v-if="state?.error">{{ state.error }}</p>
31
+ <button :disabled="!state?.canPrevious" @click="reader?.previous()">上一页</button>
32
+ <span>{{ state?.page }} / {{ state?.pages }}</span>
33
+ <button :disabled="!state?.canNext" @click="reader?.next()">下一页</button>
34
+ </template>
35
+ ```
36
+
37
+ 也支持 `app.use(PdfFlipbook)` 全局注册,`PdfFlipbook` 为默认导出。
38
+
39
+ ## Props
40
+
41
+ | 属性 | 类型 | 默认值 | 说明 |
42
+ | --- | --- | --- | --- |
43
+ | `url` | `string` | 必填 | PDF URL,变化时重新加载 |
44
+ | `fileSize` | `number` | 自动读取 `Content-Range` | PDF 原始字节数;跨域响应未暴露 `Content-Range` 时必须由文件元数据接口提供,变化时重新加载 |
45
+ | `initialPage` | `number` | `1` | 每次加载文档的初始页码,从 1 开始 |
46
+ | `initialMode` | `'single'` / `'double'` | `'double'` | 初始模式,后续用 `setMode()` 切换 |
47
+ | `height` | `string` / `number` | `100%` | 默认填满父容器高度;数字单位为 px |
48
+ | `background` | `string` | `'transparent'` | 背景色 |
49
+ | `workerSrc` | `string` | 内置 Worker | 自定义 PDF.js Worker URL |
50
+
51
+ 双页模式在空间不足时自动显示单页。`visiblePages` 反映实际显示页,而 `mode` 表示选择的模式。
52
+
53
+ ### 自适应父容器
54
+
55
+ 父容器设置明确宽高后,组件默认占满父容器。书页按 PDF 比例等比缩放:宽度受限时铺满宽度,高度受限时铺满高度,另一方向居中留白,不裁剪或拉伸。双页按两页合计宽高比计算;可用宽度小于 520px 时自动退回单页。容器尺寸变化、全屏及缩略图显隐会自动重新计算。
56
+
57
+ ```vue
58
+ <div style="width: 100%; height: 80vh; min-width: 0; min-height: 0">
59
+ <VuePdfFlipbook :url="pdfUrl" />
60
+ </div>
61
+ ```
62
+
63
+ `height` 默认值已从 `720` 改为 `'100%'`,因此父容器必须有明确高度(使用百分比时,其祖先也需要确定高度)。需要原来的固定高度时传入 `:height="720"`。已取消单页 500px、双页 1120px 及 PDF 原始尺寸的显示上限;此处调整显示尺寸,Canvas 渲染分辨率策略保持不变。
64
+
65
+ 鼠标移入阅读区时,左侧显示“上一页”、右侧显示“下一页”按钮。按钮复用现有翻页与预加载流程,首页/末页以及目标页加载期间禁用相应操作。键盘聚焦时也会显示按钮;无悬停能力的触屏设备上保持显示。缩略图列表不占用按钮的定位区域。
66
+
67
+ ## 事件
68
+
69
+ | 事件 | 参数 | 说明 |
70
+ | --- | --- | --- |
71
+ | `loaded` | `{ pages: number }` | 文档加载与翻页引擎初始化完成,页面 Canvas 可能仍在渲染 |
72
+ | `error` | `unknown` | 加载或页面渲染错误,外部负责提示 |
73
+ | `progress` | `number` | 下载百分比,服务端未提供总长度时可能为 0 |
74
+ | `page-change` | `number` | 当前页码变化 |
75
+ | `mode-change` | `ReaderMode` | 调用 `setMode` 改变模式 |
76
+ | `state-change` | `PdfFlipbookState` | 加载、页码、布局等状态变化 |
77
+
78
+ `PdfFlipbookState` 包含 `page`、`pages`、`mode`、`visiblePages`、`loading`、`progress`、`error`(文档加载错误文本)、`canPrevious`、`canNext`。状态为快照,修改它不会修改组件。
79
+
80
+ 另外,`pageLoading` 表示跳页前正在下载或渲染目标及动画必需页面,`targetPage` 表示目标页码(准备及动画期间有效,空闲时为 `null`)。外部可监听 `state-change` 展示 `正在加载第 {{ state.targetPage }} 页`。它与首次打开文档的 `loading` 独立;页面准备完成后、动画开始前自动结束,失败、切换模式、重新加载或取消跳页时也会清除。已准备好的页面不会显示加载状态。demo 在阅读区显示遮罩和转圈提示,等待期间仍可通过进度条选择其他目标页。
81
+
82
+ ## 实例方法
83
+
84
+ `next()`、`previous()`、`goToPage()` 共用 150ms 防抖:最后一次调用后等待 150ms,再准备页面并翻页。连续点击按钮、缩略图或拖动进度条时只执行最后一次有效请求,避免为中间目标反复加载。被替换或取消的 `goToPage()` Promise 会正常结束。动画进行时继续忽略新的程序跳页;切换模式、重新加载及卸载会取消待执行请求。已就绪书页的原生拖拽仍即时响应,并保留动画期间的重复操作保护。
85
+
86
+ | 方法 | 说明 |
87
+ | --- | --- |
88
+ | `next()` / `previous()` | 翻到下一组 / 上一组页面 |
89
+ | `goToPage(page)` | 先准备目标及动画必需页面再启动翻页,返回 `Promise<void>`(不等待动画结束);页码从 1 开始 |
90
+ | `setMode(mode)` | 切换单 / 双页,返回 `Promise<void>` |
91
+ | `reload()` | 重新加载当前 URL,返回 `Promise<void>`,错误通过事件报告 |
92
+ | `getState()` | 获取当前状态快照 |
93
+ | `getDocument()` | 获取 `PDFDocumentProxy`,未加载时为 `undefined` |
94
+
95
+ 文档由组件管理,外部不要调用 `destroy()` 或 `cleanup()`。URL 变化、重新加载或组件卸载后应丢弃旧引用;Vue 中使用 `shallowRef` 保存代理对象。
96
+
97
+ ## 大文件按需预览
98
+
99
+ 默认只渲染当前可见页面,以及前后各 5 页。单页模式稳定状态最多 11 个页面 Canvas,双页模式最多 12 个;首页、末页会按文档边界裁剪。翻页后移出窗口的页面取消渲染并释放 Canvas 位图,翻回时重新渲染。所有页码保留轻量 DOM 占位以维持翻页引擎索引。
100
+
101
+ 远距离跳页时临时保留起点和目标两个窗口,不加载中间所有页面;目标及动画必需页面准备完成后立即播放动画,不等待全部前后预加载页,结束后释放旧窗口。准备期间新的跳页请求会替代旧请求;动画进行时忽略新的程序跳页请求。渲染失败通过 `error` 报告,可调用 `reload()` 重试。初次布局仍会读取第 1 页尺寸。首次展示和跳页优先渲染可见页及动画背面,再逐步补齐前后各 5 页的窗口,同时最多预加载两张尚未完成的正文页。缩略图等对应正文页就绪后再渲染,避免竞争首屏资源;正文和缩略图共享的 PDF 页面资源在最后一个使用者卸载后释放。
102
+
103
+ 连续请求相同或重叠的目标窗口会保留已完成的渲染记录。书页点击、拖拽也会检查动画必需页面:未准备好时保留当前页,通过 `pageLoading` 提示外部,准备完成后自动翻页;已准备好时保留原有拖拽交互。尚未绘制的 Canvas 隐藏,避免显示黑色底图。
104
+
105
+ 组件使用自定义 `PDFDataRangeTransport`:第一次请求就带 `Range: bytes=0-65535`,之后只请求 PDF.js 所需的字节段,关闭流式下载和自动预取。请求按 64 KiB 对齐,PDF.js 可能合并相邻段。每段校验 HTTP `206`、长度以及浏览器可见的 `Content-Range`;收到 `200` 时立即取消响应并报告错误,不再回退到整文件下载。
106
+
107
+ 跨域服务器必须允许 CORS 和 Range 请求。建议 OSS/CDN 的 `Access-Control-Expose-Headers` 包含 `Content-Range, Accept-Ranges, Content-Length, Content-Encoding, ETag`,PDF 的 `Content-Type` 设置为 `application/pdf`。组件优先从 `Content-Range` 获取总长度,不再依赖浏览器是否能读到 `Accept-Ranges`。
108
+
109
+ 若暂时不能调整服务器,可通过 `fileSize` 传入文件元数据中的**原始字节数**:
110
+
111
+ ```vue
112
+ <VuePdfFlipbook :url="file.url" :file-size="file.size" />
113
+ ```
114
+
115
+ `file.size` 应来自上传文件的 `File.size` 或后端文件元数据。不能使用可能被 gzip/br 压缩的 HEAD `Content-Length`。未暴露 `Content-Range` 且未提供 `fileSize` 时会明确报错;不会猜测长度或下载全文。demo 已为当前示例 URL 配置实测原始大小,换用其他 URL 时需暴露响应头或提供该文件自己的大小。`blob:` / `data:` URL 和不支持 Range 的服务器不适用于此分段传输模式。
116
+
117
+ PDF 的页与字节段不是一一对应:解析索引、字体及共享资源可能读取窗口之外的字节。因此这里限制的是页面渲染窗口,并尽量按需请求文件字节,不保证只下载这几页的数据,也不保证 PDF.js 已下载的数据缓存固定大小。`loaded` 表示文档可用,并不要求下载进度达到 100%。
118
+
119
+ 内置缩略图只渲染当前可见页前后各 5 页的窗口,其余页保留页码按钮,点击后按需跳转,避免缩略图触发全书渲染。
120
+
121
+ ## 外部功能接入
122
+
123
+ ### 内置缩略图与插槽
124
+
125
+ 默认隐藏缩略图。实例方法 `showThumbnails()` / `hideThumbnails()` 显示或隐藏阅读区域底部的列表,`getState()` 和 `state-change` 的 `thumbnailsVisible` 字段反映显示开关。文档尚未就绪时调用显示方法,会在加载完成后显示。切换文档保留显示开关;隐藏会卸载缩略图并释放 Canvas。
126
+
127
+ 未提供插槽时,默认列表以半透明浮层覆盖实际书页底部,宽度跟随书页,不占用布局高度,显示或隐藏不会缩小书页;单项宽度为 30px,预览图高度按 PDF 宽高比自适应。提供自定义插槽时保留组件底部列表布局和原有单项尺寸。每次打开会将当前页缩略图居中定位。默认内容只渲染当前页窗口,点击任意页码仍可按需跳转。
128
+
129
+ `thumbnail` 作用域插槽自定义每一项的内容,外层按钮、选中状态及跳页行为由组件提供,请勿在插槽内嵌套按钮或链接。参数类型 `PdfThumbnailSlotProps` 已导出:`page`(页码)、`pdf`(借用的 PDF 文档)、`isActive`(当前可见页)、`shouldRender`(位于预览窗口且对应正文页已完成渲染)。自定义 Canvas 时请使用 `shouldRender`,避免加载全书。
130
+
131
+ ```vue
132
+ <VuePdfFlipbook ref="reader" :url="url">
133
+ <template #thumbnail="{ page, pdf, isActive, shouldRender }">
134
+ <div style="width: 100%; aspect-ratio: 3 / 4">
135
+ <PdfCanvasPage v-if="shouldRender" :pdf="pdf" :page-number="page" :render-scale="0.22" />
136
+ </div>
137
+ <span :style="{ color: isActive ? '#987044' : '#333' }">第 {{ page }} 页</span>
138
+ </template>
139
+ </VuePdfFlipbook>
140
+ <button @click="reader?.showThumbnails()">显示缩略图</button>
141
+ <button @click="reader?.hideThumbnails()">隐藏缩略图</button>
142
+ ```
143
+
144
+ `reader` 使用 `ref<PdfFlipbookExpose>()`,`VuePdfFlipbook`、`PdfCanvasPage` 与类型从包入口导入。不传插槽时使用内置缩略图样式。
145
+
146
+ - 工具栏、进度滑块、键盘快捷键:调用翻页方法并监听状态事件。
147
+ - 加载提示、错误提示、重试:监听状态或 `error`,调用 `reload()`。
148
+ - 缩略图:调用 `showThumbnails()` / `hideThumbnails()`,或通过 `thumbnail` 插槽自定义单项内容,见下方示例。
149
+ - 全屏:在外部容器上调用浏览器 Fullscreen API,组件自动响应容器尺寸变化。
150
+ - 缩放和平移:由外部包装容器实现变换和滚动,不属于翻页组件 API。
151
+
152
+ `src/demo/App.vue` 展示外部控件接入:放大后按住鼠标左键拖动可平移阅读区域,松开即停止;恢复 100% 时重置滚动位置并恢复书页拖拽翻页。放大时仍可通过外部按钮翻页。平移逻辑位于 demo,核心组件不会自动处理键盘事件或创建弹层。
153
+
154
+ ## 从内置阅读器迁移
155
+
156
+ 已移除 `showToolbar`、`showThumbnails`、`minZoom`、`maxZoom`、`zoomStep`,以及 `zoomIn()`、`zoomOut()`、`resetZoom()`、`toggleFullscreen()` 和 `zoom-change`。请将对应逻辑迁移到业务组件。默认背景改为透明;组件不再附加页码和骨架屏。阅读区现提供左右翻页按钮。
157
+
158
+ ## 开发
159
+
160
+ ### 代码组织
161
+
162
+ 组件采用 Composition API 按职责组合,内部 composable 不作为包的公共导出。
163
+
164
+ | 模块 | 职责 |
165
+ | --- | --- |
166
+ | `VuePdfFlipbook.vue` | 声明组件接口、组合逻辑和模板渲染 |
167
+ | `usePdfFlipbook` | 外观层:协调文档、导航、布局和引擎,统一生成对外状态快照 |
168
+ | `usePdfDocument` | 管理 PDF 加载、进度、请求取消与文档生命周期 |
169
+ | `usePageNavigation` | 管理页码、预览窗口和跳页准备流程,通过引擎接口执行翻页 |
170
+ | `usePageFlip` | 适配 PageFlip,封装初始化、事件、Canvas 克隆和销毁 |
171
+ | `useBookLayout` | 计算容器尺寸、观察尺寸变化和调度布局更新 |
172
+ | `usePdfPageCanvas` | 管理单页 Canvas 渲染、取消及资源释放 |
173
+
174
+ 上述 composable 位于 `src/composables/`,采用单一职责、外观和适配器模式。响应式状态由各模块持有,通过只读 ref 和显式方法交互;副作用在 Vue 生命周期内注册并清理。导航依赖最小引擎接口,因此可以独立验证异步跳页和取消行为。
175
+
176
+ demo 的缩放拖动与全屏分别封装在 `src/demo/composables/useZoomPan.ts`、`useFullscreen.ts`,业务 UI 仍由 demo 组合,不进入库的公共 API。
177
+
178
+ ### 命令
179
+
180
+ 需要 Node.js 20 或更新版本。远程 PDF 必须允许浏览器 CORS 访问。
181
+
182
+ ```bash
183
+ npm install
184
+ npm run dev
185
+ npm run typecheck
186
+ npm test
187
+ npm run build
188
+ npm run pack:check
189
+ ```
190
+
191
+ `npm test` 使用 Node.js 原生测试运行器,需要 Node.js 22.15+,覆盖页面窗口、网络分段以及异步跳页的等待、替换、失败和取消场景。
192
+
193
+ ## License
194
+
195
+ MIT
196
+
197
+
198
+
199
+
200
+
@@ -0,0 +1,37 @@
1
+ import type { PdfFlipbookProps, PdfThumbnailSlotProps } from './types';
2
+ type __VLS_Slots = {
3
+ thumbnail?: (props: PdfThumbnailSlotProps) => unknown;
4
+ };
5
+ declare const __VLS_base: import("vue").DefineComponent<PdfFlipbookProps, import("./types").PdfFlipbookExpose, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
6
+ error: (error: unknown) => any;
7
+ progress: (progress: number) => any;
8
+ loaded: (payload: {
9
+ pages: number;
10
+ }) => any;
11
+ "page-change": (page: number) => any;
12
+ "mode-change": (mode: import("./types").ReaderMode) => any;
13
+ "state-change": (state: import("./types").PdfFlipbookState) => any;
14
+ }, string, import("vue").PublicProps, Readonly<PdfFlipbookProps> & Readonly<{
15
+ onError?: ((error: unknown) => any) | undefined;
16
+ onProgress?: ((progress: number) => any) | undefined;
17
+ onLoaded?: ((payload: {
18
+ pages: number;
19
+ }) => any) | undefined;
20
+ "onPage-change"?: ((page: number) => any) | undefined;
21
+ "onMode-change"?: ((mode: import("./types").ReaderMode) => any) | undefined;
22
+ "onState-change"?: ((state: import("./types").PdfFlipbookState) => any) | undefined;
23
+ }>, {
24
+ initialPage: number;
25
+ initialMode: import("./types").ReaderMode;
26
+ height: string | number;
27
+ background: string;
28
+ workerSrc: string;
29
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
30
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
31
+ declare const _default: typeof __VLS_export;
32
+ export default _default;
33
+ type __VLS_WithSlots<T, S> = T & {
34
+ new (): {
35
+ $slots: S;
36
+ };
37
+ };