pdf-flipbook 1.0.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) 2024
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,193 @@
1
+ # pdf-flipbook
2
+
3
+ > **A responsive, animated PDF flipbook viewer.**
4
+ > Give it a PDF URL and it becomes a beautiful interactive book — complete with page-turn animations, keyboard/swipe navigation, and full responsiveness.
5
+
6
+ [![npm version](https://img.shields.io/npm/v/pdf-flipbook.svg)](https://www.npmjs.com/package/pdf-flipbook)
7
+ [![license](https://img.shields.io/npm/l/pdf-flipbook.svg)](LICENSE)
8
+
9
+ ---
10
+
11
+ ## ✨ Features
12
+
13
+ - 📖 Realistic 3D page-flip animation (CSS transforms, no WebGL needed)
14
+ - 📄 Renders any PDF via **PDF.js** — no server required
15
+ - 📱 Fully responsive — two-page spread on desktop, single page on mobile
16
+ - ⌨️ Keyboard navigation (← →) and touch swipe support
17
+ - 🎨 Dark & light themes built-in, fully customisable via CSS variables
18
+ - 🧩 Works with **React**, **Vue**, vanilla JS, or any framework
19
+ - 🌐 No jQuery, no heavy UI libraries
20
+
21
+ ---
22
+
23
+ ## 📦 Installation
24
+
25
+ ```bash
26
+ npm install pdf-flipbook
27
+ # or
28
+ yarn add pdf-flipbook
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 🚀 Quick Start
34
+
35
+ ### 1 — Vanilla JS (ESM)
36
+
37
+ ```html
38
+ <div id="my-book" style="height: 600px;"></div>
39
+
40
+ <script type="module">
41
+ import PdfFlipbook from 'pdf-flipbook';
42
+ import 'pdf-flipbook/style.css';
43
+
44
+ const book = new PdfFlipbook('#my-book', 'https://example.com/document.pdf');
45
+ </script>
46
+ ```
47
+
48
+ ### 2 — Vanilla JS (UMD / CDN)
49
+
50
+ ```html
51
+ <link rel="stylesheet" href="https://unpkg.com/pdf-flipbook/dist/pdf-flipbook.css">
52
+ <script src="https://unpkg.com/pdf-flipbook/dist/pdf-flipbook.umd.js"></script>
53
+
54
+ <div id="my-book"></div>
55
+ <script>
56
+ const { PdfFlipbook } = window.PdfFlipbook;
57
+ new PdfFlipbook('#my-book', '/path/to/file.pdf');
58
+ </script>
59
+ ```
60
+
61
+ ### 3 — React
62
+
63
+ ```jsx
64
+ import { useEffect, useRef } from 'react';
65
+ import PdfFlipbook from 'pdf-flipbook';
66
+ import 'pdf-flipbook/style.css';
67
+
68
+ export default function BookViewer({ pdfUrl }) {
69
+ const ref = useRef(null);
70
+
71
+ useEffect(() => {
72
+ if (!ref.current) return;
73
+ const book = new PdfFlipbook(ref.current, pdfUrl, { theme: 'light' });
74
+ return () => book.destroy();
75
+ }, [pdfUrl]);
76
+
77
+ return <div ref={ref} style={{ height: 600 }} />;
78
+ }
79
+ ```
80
+
81
+ ### 4 — Vue 3
82
+
83
+ ```vue
84
+ <template>
85
+ <div ref="container" style="height: 600px" />
86
+ </template>
87
+
88
+ <script setup>
89
+ import { ref, onMounted, onUnmounted } from 'vue';
90
+ import PdfFlipbook from 'pdf-flipbook';
91
+ import 'pdf-flipbook/style.css';
92
+
93
+ const props = defineProps({ pdfUrl: String });
94
+ const container = ref(null);
95
+ let book;
96
+
97
+ onMounted(() => {
98
+ book = new PdfFlipbook(container.value, props.pdfUrl);
99
+ });
100
+
101
+ onUnmounted(() => book?.destroy());
102
+ </script>
103
+ ```
104
+
105
+ ---
106
+
107
+ ## ⚙️ API
108
+
109
+ ### `new PdfFlipbook(container, pdfUrl, options?)`
110
+
111
+ | Parameter | Type | Description |
112
+ |-------------|---------------------------|--------------------------------------------|
113
+ | `container` | `string \| HTMLElement` | CSS selector or DOM element to mount into |
114
+ | `pdfUrl` | `string` | URL of the PDF to display |
115
+ | `options` | `object` | Optional configuration (see below) |
116
+
117
+ ### Options
118
+
119
+ | Option | Type | Default | Description |
120
+ |--------------------|------------|-----------|----------------------------------------------------|
121
+ | `flipDuration` | `number` | `700` | Page-flip animation duration (ms) |
122
+ | `shadowIntensity` | `number` | `0.4` | Shadow opacity at peak flip (0–1) |
123
+ | `maxDpr` | `number` | `2` | Max device-pixel-ratio for canvas quality |
124
+ | `toolbar` | `boolean` | `true` | Show the bottom toolbar with page info |
125
+ | `theme` | `string` | `"dark"` | `"dark"` or `"light"` |
126
+ | `preloadAhead` | `number` | `2` | Pages to preload ahead of current spread |
127
+ | `onFlip` | `function` | `null` | Callback: `(fromPage, toPage) => void` |
128
+ | `onReady` | `function` | `null` | Callback: `() => void` — all pages loaded |
129
+ | `onError` | `function` | `null` | Callback: `(error) => void` |
130
+
131
+ ### Instance Methods
132
+
133
+ ```js
134
+ book.next() // Turn to the next spread
135
+ book.prev() // Turn to the previous spread
136
+ book.goTo(pageNum) // Jump to a specific page (1-indexed)
137
+ book.destroy() // Remove the flipbook and clean up events
138
+ ```
139
+
140
+ ### Static Properties
141
+
142
+ ```js
143
+ // Override the PDF.js worker URL (do this before instantiating):
144
+ PdfFlipbook.workerSrc = '/path/to/pdf.worker.min.mjs';
145
+ ```
146
+
147
+ ---
148
+
149
+ ## 🎨 Theming
150
+
151
+ The flipbook is fully customisable via CSS variables on the `.pfb` element:
152
+
153
+ ```css
154
+ .pfb {
155
+ --pfb-bg: #1a1a2e; /* stage background */
156
+ --pfb-page-bg: #fdfaf5; /* page background colour */
157
+ --pfb-spine-color: #0f0f1a; /* spine colour */
158
+ --pfb-spine-width: 12px; /* spine width */
159
+ --pfb-accent: #c9a96e; /* loader/accent colour */
160
+ --pfb-flip-z: 600px; /* perspective depth */
161
+ --pfb-book-shadow: 0 30px 80px rgba(0,0,0,.6);
162
+ }
163
+ ```
164
+
165
+ ---
166
+
167
+ ## 🌍 CORS & PDF Sources
168
+
169
+ PDF.js fetches the file from the browser, so the PDF server must serve the correct CORS headers:
170
+
171
+ ```
172
+ Access-Control-Allow-Origin: *
173
+ ```
174
+
175
+ If you control the server, add that header. If you're loading local files during development, serve them via a local HTTP server (not `file://`).
176
+
177
+ ---
178
+
179
+ ## 📋 Browser Support
180
+
181
+ | Browser | Support |
182
+ |---------|---------|
183
+ | Chrome 90+ | ✅ |
184
+ | Firefox 88+ | ✅ |
185
+ | Safari 14+ | ✅ |
186
+ | Edge 90+ | ✅ |
187
+ | IE 11 | ❌ |
188
+
189
+ ---
190
+
191
+ ## 📝 License
192
+
193
+ [MIT](LICENSE) © 2024
@@ -0,0 +1,304 @@
1
+ /* ============================================================
2
+ pdf-flipbook — styles
3
+ Import via: import 'pdf-flipbook/style.css'
4
+ ============================================================ */
5
+
6
+ /* ── Reset / Container ─────────────────────────────────────── */
7
+ .pfb {
8
+ --pfb-bg: #1a1a2e;
9
+ --pfb-book-shadow: 0 30px 80px rgba(0,0,0,.6);
10
+ --pfb-spine-color: #0f0f1a;
11
+ --pfb-spine-width: 12px;
12
+ --pfb-page-bg: #fdfaf5;
13
+ --pfb-toolbar-bg: rgba(10, 10, 20, 0.92);
14
+ --pfb-toolbar-color: #e8e0d0;
15
+ --pfb-btn-bg: rgba(255,255,255,.08);
16
+ --pfb-btn-hover-bg: rgba(255,255,255,.18);
17
+ --pfb-btn-color: #e8e0d0;
18
+ --pfb-accent: #c9a96e;
19
+ --pfb-radius: 4px;
20
+ --pfb-flip-z: 600px;
21
+ --pfb-duration: 0.7s;
22
+
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ gap: 0;
27
+ width: 100%;
28
+ background: var(--pfb-bg);
29
+ font-family: 'Georgia', 'Times New Roman', serif;
30
+ -webkit-font-smoothing: antialiased;
31
+ box-sizing: border-box;
32
+ user-select: none;
33
+ -webkit-user-select: none;
34
+ }
35
+
36
+ .pfb *,
37
+ .pfb *::before,
38
+ .pfb *::after {
39
+ box-sizing: inherit;
40
+ }
41
+
42
+ /* ── Light theme ────────────────────────────────────────────── */
43
+ .pfb--light {
44
+ --pfb-bg: #e8e4dc;
45
+ --pfb-toolbar-bg: rgba(240,236,228,0.96);
46
+ --pfb-toolbar-color: #3a3028;
47
+ --pfb-btn-bg: rgba(0,0,0,.07);
48
+ --pfb-btn-hover-bg: rgba(0,0,0,.14);
49
+ --pfb-btn-color: #3a3028;
50
+ --pfb-book-shadow: 0 20px 60px rgba(0,0,0,.3);
51
+ }
52
+
53
+ /* ── Stage ──────────────────────────────────────────────────── */
54
+ .pfb__stage {
55
+ position: relative;
56
+ width: 100%;
57
+ display: flex;
58
+ justify-content: center;
59
+ align-items: center;
60
+ padding: 32px 16px 20px;
61
+ overflow: hidden;
62
+ min-height: 300px;
63
+ }
64
+
65
+ /* ── Book ───────────────────────────────────────────────────── */
66
+ .pfb__book {
67
+ position: relative;
68
+ display: flex;
69
+ align-items: stretch;
70
+ perspective: var(--pfb-flip-z);
71
+ box-shadow: var(--pfb-book-shadow);
72
+ max-width: min(92vw, 1100px);
73
+ width: 100%;
74
+ }
75
+
76
+ /* ── Page panels ────────────────────────────────────────────── */
77
+ .pfb__page {
78
+ position: relative;
79
+ flex: 1;
80
+ transform-style: preserve-3d;
81
+ transition: transform var(--pfb-duration) cubic-bezier(.645,.045,.355,1);
82
+ overflow: hidden;
83
+ min-height: 300px;
84
+ }
85
+
86
+ .pfb__page--left { transform-origin: right center; border-radius: var(--pfb-radius) 0 0 var(--pfb-radius); }
87
+ .pfb__page--right { transform-origin: left center; border-radius: 0 var(--pfb-radius) var(--pfb-radius) 0; }
88
+
89
+ /* ── Page faces (front / back) ──────────────────────────────── */
90
+ .pfb__page-front,
91
+ .pfb__page-back {
92
+ position: absolute;
93
+ inset: 0;
94
+ background: var(--pfb-page-bg);
95
+ backface-visibility: hidden;
96
+ -webkit-backface-visibility: hidden;
97
+ overflow: hidden;
98
+ }
99
+
100
+ .pfb__page-back {
101
+ transform: rotateY(180deg);
102
+ }
103
+
104
+ .pfb__page-front canvas,
105
+ .pfb__page-back canvas {
106
+ display: block;
107
+ width: 100% !important;
108
+ height: 100% !important;
109
+ object-fit: contain;
110
+ }
111
+
112
+ .pfb__face--blank {
113
+ background: linear-gradient(135deg, #fdfaf5 0%, #f0e8d8 100%);
114
+ }
115
+
116
+ /* ── Spine ──────────────────────────────────────────────────── */
117
+ .pfb__spine {
118
+ flex-shrink: 0;
119
+ width: var(--pfb-spine-width);
120
+ background: var(--pfb-spine-color);
121
+ position: relative;
122
+ z-index: 10;
123
+ }
124
+
125
+ .pfb__spine::before {
126
+ content: '';
127
+ position: absolute;
128
+ inset: 0;
129
+ background: linear-gradient(
130
+ to right,
131
+ rgba(255,255,255,.05) 0%,
132
+ rgba(0,0,0,.4) 50%,
133
+ rgba(255,255,255,.05) 100%
134
+ );
135
+ }
136
+
137
+ /* ── Shadows ────────────────────────────────────────────────── */
138
+ .pfb__shadow {
139
+ position: absolute;
140
+ inset: 0;
141
+ pointer-events: none;
142
+ opacity: 0;
143
+ transition: opacity 0.1s linear;
144
+ z-index: 5;
145
+ }
146
+
147
+ .pfb__shadow--left { background: linear-gradient(to left, rgba(0,0,0,.4), transparent 80%); }
148
+ .pfb__shadow--right { background: linear-gradient(to right, rgba(0,0,0,.4), transparent 80%); }
149
+
150
+ /* ── Nav arrow buttons (on stage) ───────────────────────────── */
151
+ .pfb__btn {
152
+ position: absolute;
153
+ top: 50%;
154
+ transform: translateY(-50%);
155
+ width: 44px;
156
+ height: 44px;
157
+ border: none;
158
+ border-radius: 50%;
159
+ background: var(--pfb-btn-bg);
160
+ color: var(--pfb-btn-color);
161
+ font-size: 20px;
162
+ cursor: pointer;
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ transition: background 0.2s, transform 0.2s, opacity 0.2s;
167
+ z-index: 20;
168
+ backdrop-filter: blur(4px);
169
+ -webkit-backdrop-filter: blur(4px);
170
+ }
171
+
172
+ .pfb__btn--prev { left: 8px; }
173
+ .pfb__btn--next { right: 8px; }
174
+
175
+ .pfb__btn:hover:not(:disabled) {
176
+ background: var(--pfb-btn-hover-bg);
177
+ transform: translateY(-50%) scale(1.1);
178
+ }
179
+
180
+ .pfb__btn:disabled {
181
+ opacity: 0.2;
182
+ cursor: not-allowed;
183
+ }
184
+
185
+ /* ── Toolbar ────────────────────────────────────────────────── */
186
+ .pfb__toolbar {
187
+ width: 100%;
188
+ display: flex;
189
+ align-items: center;
190
+ justify-content: center;
191
+ gap: 16px;
192
+ padding: 10px 16px;
193
+ background: var(--pfb-toolbar-bg);
194
+ backdrop-filter: blur(8px);
195
+ -webkit-backdrop-filter: blur(8px);
196
+ border-top: 1px solid rgba(255,255,255,.07);
197
+ }
198
+
199
+ .pfb__tb-btn {
200
+ width: 36px;
201
+ height: 36px;
202
+ border: 1px solid rgba(255,255,255,.12);
203
+ border-radius: 6px;
204
+ background: var(--pfb-btn-bg);
205
+ color: var(--pfb-toolbar-color);
206
+ font-size: 16px;
207
+ cursor: pointer;
208
+ display: flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ transition: background 0.2s;
212
+ }
213
+
214
+ .pfb__tb-btn:hover:not(:disabled) { background: var(--pfb-btn-hover-bg); }
215
+ .pfb__tb-btn:disabled { opacity: 0.25; cursor: not-allowed; }
216
+
217
+ .pfb__page-info {
218
+ font-size: 13px;
219
+ letter-spacing: 0.05em;
220
+ color: var(--pfb-toolbar-color);
221
+ min-width: 90px;
222
+ text-align: center;
223
+ font-family: 'Georgia', serif;
224
+ }
225
+
226
+ /* ── Loader ─────────────────────────────────────────────────── */
227
+ .pfb__loader {
228
+ position: absolute;
229
+ inset: 0;
230
+ display: flex;
231
+ flex-direction: column;
232
+ align-items: center;
233
+ justify-content: center;
234
+ gap: 16px;
235
+ background: rgba(10,10,20,.85);
236
+ z-index: 30;
237
+ backdrop-filter: blur(4px);
238
+ -webkit-backdrop-filter: blur(4px);
239
+ }
240
+
241
+ .pfb__loader[hidden] { display: none; }
242
+
243
+ .pfb__spinner {
244
+ width: 48px;
245
+ height: 48px;
246
+ border: 3px solid rgba(201,169,110,.2);
247
+ border-top-color: var(--pfb-accent);
248
+ border-radius: 50%;
249
+ animation: pfb-spin 0.9s linear infinite;
250
+ }
251
+
252
+ @keyframes pfb-spin {
253
+ to { transform: rotate(360deg); }
254
+ }
255
+
256
+ .pfb__loader-text {
257
+ font-size: 13px;
258
+ color: var(--pfb-toolbar-color, #c9a96e);
259
+ letter-spacing: 0.08em;
260
+ text-transform: uppercase;
261
+ }
262
+
263
+ /* ── Error ──────────────────────────────────────────────────── */
264
+ .pfb__error {
265
+ position: absolute;
266
+ inset: 0;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ padding: 24px;
271
+ text-align: center;
272
+ color: #e07070;
273
+ font-size: 14px;
274
+ background: rgba(10,10,20,.9);
275
+ z-index: 30;
276
+ }
277
+
278
+ .pfb__error[hidden] { display: none; }
279
+
280
+ /* ── Responsive ─────────────────────────────────────────────── */
281
+ @media (max-width: 640px) {
282
+ /* On narrow screens show one page at a time */
283
+ .pfb__book {
284
+ max-width: min(96vw, 520px);
285
+ }
286
+
287
+ .pfb__page--left {
288
+ display: none;
289
+ }
290
+
291
+ .pfb__spine {
292
+ display: none;
293
+ }
294
+
295
+ .pfb__page--right {
296
+ border-radius: var(--pfb-radius);
297
+ }
298
+
299
+ .pfb__btn {
300
+ width: 38px;
301
+ height: 38px;
302
+ font-size: 16px;
303
+ }
304
+ }