pdf-flipbook 1.3.0 → 1.8.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/dist/pdf-flipbook.css +94 -189
- package/dist/pdf-flipbook.esm.js +325 -210
- package/dist/pdf-flipbook.esm.js.map +3 -3
- package/dist/pdf-flipbook.esm.min.js +16 -32
- package/dist/pdf-flipbook.esm.min.js.map +3 -3
- package/dist/pdf-flipbook.umd.js +325 -210
- package/dist/pdf-flipbook.umd.js.map +3 -3
- package/package.json +4 -3
package/dist/pdf-flipbook.esm.js
CHANGED
|
@@ -21209,291 +21209,406 @@ if (typeof window !== "undefined") {
|
|
|
21209
21209
|
__webpack_exports__GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${__webpack_exports__version}/pdf.worker.min.mjs`;
|
|
21210
21210
|
}
|
|
21211
21211
|
var DEFAULTS = {
|
|
21212
|
-
|
|
21213
|
-
flipDuration: 700,
|
|
21214
|
-
/** Shadow opacity at peak of flip (0-1) */
|
|
21215
|
-
shadowIntensity: 0.4,
|
|
21216
|
-
/** Device-pixel-ratio cap for canvas rendering */
|
|
21217
|
-
maxDpr: 2,
|
|
21218
|
-
/** Whether to show the toolbar */
|
|
21212
|
+
flippingTime: 800,
|
|
21219
21213
|
toolbar: true,
|
|
21220
|
-
/** Toolbar theme: "light" | "dark" */
|
|
21221
21214
|
theme: "dark",
|
|
21222
|
-
|
|
21223
|
-
preloadAhead: 2,
|
|
21224
|
-
/** Called when a page turn starts: (fromPage, toPage) */
|
|
21215
|
+
maxDpr: 2,
|
|
21225
21216
|
onFlip: null,
|
|
21226
|
-
/** Called when all pages are loaded */
|
|
21227
21217
|
onReady: null,
|
|
21228
|
-
/** Called on load error */
|
|
21229
21218
|
onError: null
|
|
21230
21219
|
};
|
|
21231
|
-
var clamp = (v,
|
|
21232
|
-
var
|
|
21233
|
-
var isTouchDevice = () => "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
21220
|
+
var clamp = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
21221
|
+
var easeInOut = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
21234
21222
|
var PdfFlipbook = class {
|
|
21235
|
-
/**
|
|
21236
|
-
* @param {string|HTMLElement} container CSS selector or DOM element
|
|
21237
|
-
* @param {string} pdfUrl Remote or local PDF URL
|
|
21238
|
-
* @param {object} [options] See DEFAULTS
|
|
21239
|
-
*/
|
|
21240
21223
|
constructor(container, pdfUrl, options = {}) {
|
|
21241
21224
|
if (!pdfUrl) throw new Error("[PdfFlipbook] pdfUrl is required");
|
|
21242
21225
|
this._el = typeof container === "string" ? document.querySelector(container) : container;
|
|
21243
|
-
if (!this._el) throw new Error(
|
|
21226
|
+
if (!this._el) throw new Error("[PdfFlipbook] container not found");
|
|
21244
21227
|
this._url = pdfUrl;
|
|
21245
21228
|
this._opts = { ...DEFAULTS, ...options };
|
|
21246
21229
|
this._pages = [];
|
|
21247
|
-
this._pdf = null;
|
|
21248
21230
|
this._total = 0;
|
|
21249
21231
|
this._spread = 0;
|
|
21250
|
-
this.
|
|
21232
|
+
this._flip = null;
|
|
21233
|
+
this._raf = null;
|
|
21234
|
+
this._aspect = 1.414;
|
|
21251
21235
|
this._init();
|
|
21252
21236
|
}
|
|
21253
|
-
// ── Static workerSrc override ──────────────────────────────────────────────
|
|
21254
21237
|
static set workerSrc(src) {
|
|
21255
21238
|
__webpack_exports__GlobalWorkerOptions.workerSrc = src;
|
|
21256
21239
|
}
|
|
21257
|
-
// ═══ PUBLIC API ═══════════════════════════════════════════════════════════
|
|
21258
|
-
/** Go to the next spread */
|
|
21259
21240
|
next() {
|
|
21260
|
-
this._flip(1);
|
|
21241
|
+
if (!this._flip) this._startFlip(1);
|
|
21261
21242
|
}
|
|
21262
|
-
/** Go to the previous spread */
|
|
21263
21243
|
prev() {
|
|
21264
|
-
this._flip(-1);
|
|
21244
|
+
if (!this._flip) this._startFlip(-1);
|
|
21265
21245
|
}
|
|
21266
|
-
|
|
21267
|
-
|
|
21268
|
-
const
|
|
21269
|
-
|
|
21270
|
-
if (targetSpread === this._spread) return;
|
|
21271
|
-
const dir = targetSpread > this._spread ? 1 : -1;
|
|
21272
|
-
this._animateFlip(dir, targetSpread);
|
|
21246
|
+
goTo(n) {
|
|
21247
|
+
const idx = clamp(n - 1, 0, this._total - 1);
|
|
21248
|
+
const s = idx % 2 === 0 ? idx : idx - 1;
|
|
21249
|
+
if (s !== this._spread && !this._flip) this._startFlip(s > this._spread ? 1 : -1, s);
|
|
21273
21250
|
}
|
|
21274
|
-
/** Destroy the flipbook and clean up */
|
|
21275
21251
|
destroy() {
|
|
21276
|
-
this.
|
|
21277
|
-
this._el.classList.remove("pfb", `pfb--${this._opts.theme}`);
|
|
21278
|
-
this._pages = [];
|
|
21279
|
-
window.removeEventListener("resize", this._onResize);
|
|
21252
|
+
cancelAnimationFrame(this._raf);
|
|
21280
21253
|
window.removeEventListener("keydown", this._onKey);
|
|
21254
|
+
window.removeEventListener("resize", this._onResize);
|
|
21255
|
+
this._el.innerHTML = "";
|
|
21281
21256
|
}
|
|
21282
|
-
// ═══ PRIVATE ══════════════════════════════════════════════════════════════
|
|
21283
21257
|
async _init() {
|
|
21284
21258
|
this._buildShell();
|
|
21285
21259
|
try {
|
|
21286
|
-
await this.
|
|
21260
|
+
await this._load();
|
|
21287
21261
|
this._bindEvents();
|
|
21262
|
+
this._raf = requestAnimationFrame(() => this._loop());
|
|
21288
21263
|
this._opts.onReady?.();
|
|
21289
|
-
} catch (
|
|
21290
|
-
console.error("[PdfFlipbook]",
|
|
21291
|
-
this._showError(
|
|
21292
|
-
this._opts.onError?.(
|
|
21264
|
+
} catch (e) {
|
|
21265
|
+
console.error("[PdfFlipbook]", e);
|
|
21266
|
+
this._showError(e.message);
|
|
21267
|
+
this._opts.onError?.(e);
|
|
21293
21268
|
}
|
|
21294
21269
|
}
|
|
21295
|
-
// ── DOM Shell ──────────────────────────────────────────────────────────────
|
|
21296
21270
|
_buildShell() {
|
|
21297
21271
|
this._el.classList.add("pfb", `pfb--${this._opts.theme}`);
|
|
21298
21272
|
this._el.innerHTML = `
|
|
21299
21273
|
<div class="pfb__stage">
|
|
21300
|
-
<
|
|
21301
|
-
|
|
21302
|
-
|
|
21303
|
-
|
|
21304
|
-
|
|
21305
|
-
</div>
|
|
21306
|
-
<div class="pfb__spine"></div>
|
|
21307
|
-
<div class="pfb__page pfb__page--right">
|
|
21308
|
-
<div class="pfb__page-front"></div>
|
|
21309
|
-
<div class="pfb__page-back"></div>
|
|
21310
|
-
<div class="pfb__shadow pfb__shadow--right"></div>
|
|
21311
|
-
</div>
|
|
21312
|
-
</div>
|
|
21313
|
-
<button class="pfb__btn pfb__btn--prev" aria-label="Previous page">←</button>
|
|
21314
|
-
<button class="pfb__btn pfb__btn--next" aria-label="Next page">→</button>
|
|
21315
|
-
<div class="pfb__loader">
|
|
21316
|
-
<div class="pfb__spinner"></div>
|
|
21317
|
-
<span class="pfb__loader-text">Loading PDF\u2026</span>
|
|
21318
|
-
</div>
|
|
21274
|
+
<canvas class="pfb__canvas"></canvas>
|
|
21275
|
+
<button class="pfb__btn pfb__btn--prev">←</button>
|
|
21276
|
+
<button class="pfb__btn pfb__btn--next">→</button>
|
|
21277
|
+
<button class="pfb__btn--fs" aria-label="Fullscreen">⛶</button>
|
|
21278
|
+
<div class="pfb__loader"><div class="pfb__spinner"></div><span class="pfb__loader-text">Loading\u2026</span></div>
|
|
21319
21279
|
<div class="pfb__error" hidden></div>
|
|
21320
21280
|
</div>
|
|
21321
|
-
${this._opts.toolbar ?
|
|
21322
|
-
|
|
21323
|
-
<button class="pfb__tb-btn pfb__tb-prev" aria-label="Previous">←</button>
|
|
21281
|
+
${this._opts.toolbar ? `<div class="pfb__toolbar">
|
|
21282
|
+
<button class="pfb__tb-btn pfb__tb-prev">←</button>
|
|
21324
21283
|
<span class="pfb__page-info"></span>
|
|
21325
|
-
<button class="pfb__tb-btn pfb__tb-next"
|
|
21326
|
-
</div>` : ""}
|
|
21327
|
-
`;
|
|
21284
|
+
<button class="pfb__tb-btn pfb__tb-next">→</button>
|
|
21285
|
+
</div>` : ""}`;
|
|
21328
21286
|
this._stage = this._el.querySelector(".pfb__stage");
|
|
21329
|
-
this.
|
|
21287
|
+
this._canvas = this._el.querySelector(".pfb__canvas");
|
|
21288
|
+
this._ctx = this._canvas.getContext("2d");
|
|
21330
21289
|
this._loader = this._el.querySelector(".pfb__loader");
|
|
21290
|
+
this._loaderT = this._el.querySelector(".pfb__loader-text");
|
|
21331
21291
|
this._errorEl = this._el.querySelector(".pfb__error");
|
|
21332
|
-
this.
|
|
21333
|
-
this.
|
|
21334
|
-
|
|
21335
|
-
|
|
21336
|
-
|
|
21337
|
-
|
|
21338
|
-
this.
|
|
21339
|
-
|
|
21340
|
-
|
|
21341
|
-
|
|
21342
|
-
|
|
21343
|
-
|
|
21344
|
-
|
|
21345
|
-
|
|
21346
|
-
|
|
21347
|
-
|
|
21348
|
-
|
|
21349
|
-
async _prerenderAll() {
|
|
21350
|
-
this._loaderText = this._el.querySelector(".pfb__loader-text");
|
|
21292
|
+
this._pgInfo = this._el.querySelector(".pfb__page-info");
|
|
21293
|
+
this._resizeCanvas();
|
|
21294
|
+
}
|
|
21295
|
+
_resizeCanvas() {
|
|
21296
|
+
const w = this._stage.clientWidth || 900;
|
|
21297
|
+
const h = this._stage.clientHeight || 560;
|
|
21298
|
+
this._canvas.width = w;
|
|
21299
|
+
this._canvas.height = h;
|
|
21300
|
+
this._canvas.style.width = w + "px";
|
|
21301
|
+
this._canvas.style.height = h + "px";
|
|
21302
|
+
}
|
|
21303
|
+
async _load() {
|
|
21304
|
+
const pdf = await __webpack_exports__getDocument({ url: this._url, withCredentials: false }).promise;
|
|
21305
|
+
this._total = pdf.numPages;
|
|
21306
|
+
const fp = await pdf.getPage(1);
|
|
21307
|
+
const vp0 = fp.getViewport({ scale: 1 });
|
|
21308
|
+
this._aspect = vp0.height / vp0.width;
|
|
21351
21309
|
for (let i = 1; i <= this._total; i++) {
|
|
21352
|
-
|
|
21353
|
-
|
|
21354
|
-
|
|
21355
|
-
|
|
21356
|
-
}
|
|
21357
|
-
|
|
21358
|
-
|
|
21359
|
-
|
|
21360
|
-
|
|
21361
|
-
|
|
21362
|
-
|
|
21363
|
-
|
|
21364
|
-
const scale = baseW / viewport.width;
|
|
21365
|
-
const vp = page.getViewport({ scale: scale * dpr });
|
|
21366
|
-
const canvas = document.createElement("canvas");
|
|
21367
|
-
canvas.width = vp.width;
|
|
21368
|
-
canvas.height = vp.height;
|
|
21369
|
-
canvas.style.width = `${vp.width / dpr}px`;
|
|
21370
|
-
canvas.style.height = `${vp.height / dpr}px`;
|
|
21371
|
-
canvas.dataset.pageNum = pageNum;
|
|
21372
|
-
const ctx = canvas.getContext("2d");
|
|
21373
|
-
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
|
21374
|
-
return canvas;
|
|
21375
|
-
}
|
|
21376
|
-
// ── Spread Rendering ───────────────────────────────────────────────────────
|
|
21377
|
-
_renderSpread(spreadIndex, animate) {
|
|
21378
|
-
this._spread = clamp(spreadIndex, 0, even(this._total - 1) || 0);
|
|
21379
|
-
const leftIdx = this._spread;
|
|
21380
|
-
const rightIdx = this._spread + 1;
|
|
21381
|
-
this._setFace(this._leftFront, leftIdx);
|
|
21382
|
-
this._setFace(this._rightFront, rightIdx);
|
|
21383
|
-
this._setFace(this._leftBack, null);
|
|
21384
|
-
this._setFace(this._rightBack, null);
|
|
21310
|
+
if (this._loaderT) this._loaderT.textContent = `Loading ${i} / ${this._total}\u2026`;
|
|
21311
|
+
const page = await pdf.getPage(i);
|
|
21312
|
+
const dpr = clamp(window.devicePixelRatio || 1, 1, this._opts.maxDpr);
|
|
21313
|
+
const W = 1200 * dpr;
|
|
21314
|
+
const vp = page.getViewport({ scale: W / vp0.width });
|
|
21315
|
+
const cvs = document.createElement("canvas");
|
|
21316
|
+
cvs.width = vp.width;
|
|
21317
|
+
cvs.height = vp.height;
|
|
21318
|
+
await page.render({ canvasContext: cvs.getContext("2d"), viewport: vp }).promise;
|
|
21319
|
+
this._pages.push(cvs);
|
|
21320
|
+
}
|
|
21321
|
+
this._loader.hidden = true;
|
|
21385
21322
|
this._updateUI();
|
|
21386
21323
|
}
|
|
21387
|
-
|
|
21388
|
-
|
|
21389
|
-
|
|
21390
|
-
|
|
21324
|
+
// ── Layout ────────────────────────────────────────────────────────────────
|
|
21325
|
+
_bookLayout() {
|
|
21326
|
+
const cW = this._canvas.width;
|
|
21327
|
+
const cH = this._canvas.height;
|
|
21328
|
+
const mob = cW < 560;
|
|
21329
|
+
const arrowPad = mob ? 48 : 64;
|
|
21330
|
+
const maxW = cW - arrowPad * 2;
|
|
21331
|
+
let pH = cH - 8;
|
|
21332
|
+
let pW = Math.floor(pH / this._aspect);
|
|
21333
|
+
if (!mob && pW * 2 > maxW) {
|
|
21334
|
+
pW = Math.floor(maxW / 2);
|
|
21335
|
+
pH = Math.floor(pW * this._aspect);
|
|
21336
|
+
}
|
|
21337
|
+
if (mob && pW > maxW) {
|
|
21338
|
+
pW = maxW;
|
|
21339
|
+
pH = Math.floor(pW * this._aspect);
|
|
21340
|
+
}
|
|
21341
|
+
const bx = Math.floor((cW - (mob ? pW : pW * 2)) / 2);
|
|
21342
|
+
const by = Math.floor((cH - pH) / 2);
|
|
21343
|
+
return { bx, by, pW, pH, mob };
|
|
21344
|
+
}
|
|
21345
|
+
// ── Render loop ───────────────────────────────────────────────────────────
|
|
21346
|
+
_loop() {
|
|
21347
|
+
this._draw();
|
|
21348
|
+
this._raf = requestAnimationFrame(() => this._loop());
|
|
21349
|
+
}
|
|
21350
|
+
_draw() {
|
|
21351
|
+
const ctx = this._ctx;
|
|
21352
|
+
ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
|
21353
|
+
if (!this._total) return;
|
|
21354
|
+
const { bx, by, pW, pH, mob } = this._bookLayout();
|
|
21355
|
+
if (mob) {
|
|
21356
|
+
this._blitPage(ctx, this._pages[this._spread] || null, bx, by, pW, pH);
|
|
21391
21357
|
return;
|
|
21392
21358
|
}
|
|
21393
|
-
|
|
21394
|
-
|
|
21395
|
-
|
|
21396
|
-
|
|
21397
|
-
|
|
21398
|
-
|
|
21399
|
-
|
|
21400
|
-
|
|
21401
|
-
|
|
21402
|
-
|
|
21403
|
-
|
|
21404
|
-
|
|
21405
|
-
|
|
21406
|
-
|
|
21407
|
-
|
|
21408
|
-
|
|
21409
|
-
|
|
21410
|
-
|
|
21411
|
-
const
|
|
21412
|
-
const
|
|
21413
|
-
|
|
21414
|
-
|
|
21359
|
+
if (!this._flip) {
|
|
21360
|
+
this._drawSpread(ctx, this._spread, bx, by, pW, pH);
|
|
21361
|
+
} else {
|
|
21362
|
+
this._drawFlip(ctx, bx, by, pW, pH);
|
|
21363
|
+
}
|
|
21364
|
+
}
|
|
21365
|
+
// ── Draw a static spread ──────────────────────────────────────────────────
|
|
21366
|
+
_drawSpread(ctx, spread, bx, by, pW, pH) {
|
|
21367
|
+
this._blitPage(ctx, this._pages[spread] || null, bx, by, pW, pH);
|
|
21368
|
+
this._blitPage(ctx, this._pages[spread + 1] || null, bx + pW, by, pW, pH);
|
|
21369
|
+
this._drawSpineAndShadow(ctx, bx, by, pW, pH);
|
|
21370
|
+
}
|
|
21371
|
+
_blitPage(ctx, cvs, x, y, w, h) {
|
|
21372
|
+
ctx.fillStyle = "#ffffff";
|
|
21373
|
+
ctx.fillRect(x, y, w, h);
|
|
21374
|
+
if (cvs) ctx.drawImage(cvs, x, y, w, h);
|
|
21375
|
+
}
|
|
21376
|
+
_drawSpineAndShadow(ctx, bx, by, pW, pH) {
|
|
21377
|
+
const sx = bx + pW;
|
|
21378
|
+
const sg = ctx.createLinearGradient(sx - 18, 0, sx + 18, 0);
|
|
21379
|
+
sg.addColorStop(0, "rgba(0,0,0,0)");
|
|
21380
|
+
sg.addColorStop(0.35, "rgba(0,0,0,0.12)");
|
|
21381
|
+
sg.addColorStop(0.5, "rgba(0,0,0,0.18)");
|
|
21382
|
+
sg.addColorStop(0.65, "rgba(0,0,0,0.12)");
|
|
21383
|
+
sg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21384
|
+
ctx.fillStyle = sg;
|
|
21385
|
+
ctx.fillRect(sx - 18, by, 36, pH);
|
|
21386
|
+
const dg = ctx.createLinearGradient(0, by + pH - 4, 0, by + pH + 38);
|
|
21387
|
+
dg.addColorStop(0, "rgba(0,0,0,0.38)");
|
|
21388
|
+
dg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21389
|
+
ctx.fillStyle = dg;
|
|
21390
|
+
ctx.fillRect(bx - 16, by + pH - 4, pW * 2 + 32, 42);
|
|
21391
|
+
}
|
|
21392
|
+
// ── Flip render ───────────────────────────────────────────────────────────
|
|
21393
|
+
_drawFlip(ctx, bx, by, pW, pH) {
|
|
21394
|
+
const f = this._flip;
|
|
21395
|
+
const t = easeInOut(f.progress);
|
|
21396
|
+
const dir = f.dir;
|
|
21397
|
+
const srcSpread = f.src;
|
|
21398
|
+
const destSpread = f.dest;
|
|
21399
|
+
const srcL = this._pages[srcSpread] || null;
|
|
21400
|
+
const srcR = this._pages[srcSpread + 1] || null;
|
|
21401
|
+
const dstL = this._pages[destSpread] || null;
|
|
21402
|
+
const dstR = this._pages[destSpread + 1] || null;
|
|
21403
|
+
const spine = bx + pW;
|
|
21415
21404
|
if (dir === 1) {
|
|
21416
|
-
this.
|
|
21417
|
-
this.
|
|
21405
|
+
this._blitPage(ctx, dstR, spine, by, pW, pH);
|
|
21406
|
+
this._blitPage(ctx, srcL, bx, by, pW, pH);
|
|
21407
|
+
if (t <= 0.5) {
|
|
21408
|
+
const frontT = 1 - t * 2;
|
|
21409
|
+
const frontW = Math.floor(pW * frontT);
|
|
21410
|
+
if (frontW > 0) {
|
|
21411
|
+
ctx.save();
|
|
21412
|
+
ctx.beginPath();
|
|
21413
|
+
ctx.rect(spine, by, frontW, pH);
|
|
21414
|
+
ctx.clip();
|
|
21415
|
+
this._blitPage(ctx, srcR, spine, by, pW, pH);
|
|
21416
|
+
ctx.restore();
|
|
21417
|
+
}
|
|
21418
|
+
const shadowW = Math.min(80, pW * 0.3) * (1 - frontT);
|
|
21419
|
+
if (shadowW > 0) {
|
|
21420
|
+
const shadowX = spine - shadowW;
|
|
21421
|
+
const sg = ctx.createLinearGradient(spine, 0, shadowX, 0);
|
|
21422
|
+
sg.addColorStop(0, "rgba(0,0,0,0.38)");
|
|
21423
|
+
sg.addColorStop(0.5, "rgba(0,0,0,0.12)");
|
|
21424
|
+
sg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21425
|
+
ctx.fillStyle = sg;
|
|
21426
|
+
ctx.fillRect(shadowX, by, shadowW, pH);
|
|
21427
|
+
}
|
|
21428
|
+
const cx = spine + frontW;
|
|
21429
|
+
this._drawCrease(ctx, cx, by, pH, "right");
|
|
21430
|
+
} else {
|
|
21431
|
+
const backT = (t - 0.5) * 2;
|
|
21432
|
+
const backW = Math.floor(pW * backT);
|
|
21433
|
+
if (backW > 0) {
|
|
21434
|
+
const backX = spine - backW;
|
|
21435
|
+
ctx.save();
|
|
21436
|
+
ctx.beginPath();
|
|
21437
|
+
ctx.rect(backX, by, backW, pH);
|
|
21438
|
+
ctx.clip();
|
|
21439
|
+
ctx.save();
|
|
21440
|
+
ctx.translate(spine, by);
|
|
21441
|
+
ctx.scale(-1, 1);
|
|
21442
|
+
this._blitPage(ctx, dstL, 0, 0, pW, pH);
|
|
21443
|
+
ctx.fillStyle = `rgba(0,0,0,${0.08 + 0.12 * backT})`;
|
|
21444
|
+
ctx.fillRect(0, 0, pW, pH);
|
|
21445
|
+
ctx.restore();
|
|
21446
|
+
ctx.restore();
|
|
21447
|
+
}
|
|
21448
|
+
const shadowW = Math.min(80, pW * 0.3) * (1 - backT);
|
|
21449
|
+
if (shadowW > 0) {
|
|
21450
|
+
const shadowX = spine - backW - shadowW;
|
|
21451
|
+
const sg = ctx.createLinearGradient(spine - backW, 0, shadowX, 0);
|
|
21452
|
+
sg.addColorStop(0, "rgba(0,0,0,0.3)");
|
|
21453
|
+
sg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21454
|
+
ctx.fillStyle = sg;
|
|
21455
|
+
ctx.fillRect(Math.max(bx, shadowX), by, Math.min(shadowW, spine - backW - bx), pH);
|
|
21456
|
+
}
|
|
21457
|
+
const cx = spine - backW;
|
|
21458
|
+
this._drawCrease(ctx, cx, by, pH, "left");
|
|
21459
|
+
}
|
|
21460
|
+
} else {
|
|
21461
|
+
this._blitPage(ctx, dstL, bx, by, pW, pH);
|
|
21462
|
+
this._blitPage(ctx, srcR, spine, by, pW, pH);
|
|
21463
|
+
if (t <= 0.5) {
|
|
21464
|
+
const frontT = 1 - t * 2;
|
|
21465
|
+
const frontW = Math.floor(pW * frontT);
|
|
21466
|
+
const frontX = spine - frontW;
|
|
21467
|
+
if (frontW > 0) {
|
|
21468
|
+
ctx.save();
|
|
21469
|
+
ctx.beginPath();
|
|
21470
|
+
ctx.rect(frontX, by, frontW, pH);
|
|
21471
|
+
ctx.clip();
|
|
21472
|
+
this._blitPage(ctx, srcL, bx, by, pW, pH);
|
|
21473
|
+
ctx.restore();
|
|
21474
|
+
}
|
|
21475
|
+
const shadowW = Math.min(80, pW * 0.3) * (1 - frontT);
|
|
21476
|
+
if (shadowW > 0) {
|
|
21477
|
+
const sg = ctx.createLinearGradient(spine, 0, spine + shadowW, 0);
|
|
21478
|
+
sg.addColorStop(0, "rgba(0,0,0,0.38)");
|
|
21479
|
+
sg.addColorStop(0.5, "rgba(0,0,0,0.12)");
|
|
21480
|
+
sg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21481
|
+
ctx.fillStyle = sg;
|
|
21482
|
+
ctx.fillRect(spine, by, shadowW, pH);
|
|
21483
|
+
}
|
|
21484
|
+
const cx = spine - frontW;
|
|
21485
|
+
this._drawCrease(ctx, cx, by, pH, "left");
|
|
21486
|
+
} else {
|
|
21487
|
+
const backT = (t - 0.5) * 2;
|
|
21488
|
+
const backW = Math.floor(pW * backT);
|
|
21489
|
+
const backX = spine;
|
|
21490
|
+
if (backW > 0) {
|
|
21491
|
+
ctx.save();
|
|
21492
|
+
ctx.beginPath();
|
|
21493
|
+
ctx.rect(backX, by, backW, pH);
|
|
21494
|
+
ctx.clip();
|
|
21495
|
+
ctx.save();
|
|
21496
|
+
ctx.translate(spine, by);
|
|
21497
|
+
ctx.scale(-1, 1);
|
|
21498
|
+
ctx.translate(-pW, 0);
|
|
21499
|
+
this._blitPage(ctx, dstR, 0, 0, pW, pH);
|
|
21500
|
+
ctx.fillStyle = `rgba(0,0,0,${0.08 + 0.12 * backT})`;
|
|
21501
|
+
ctx.fillRect(0, 0, pW, pH);
|
|
21502
|
+
ctx.restore();
|
|
21503
|
+
ctx.restore();
|
|
21504
|
+
}
|
|
21505
|
+
const shadowW = Math.min(80, pW * 0.3) * (1 - backT);
|
|
21506
|
+
if (shadowW > 0) {
|
|
21507
|
+
const shadowEdge = spine + backW + shadowW;
|
|
21508
|
+
const sg = ctx.createLinearGradient(spine + backW, 0, shadowEdge, 0);
|
|
21509
|
+
sg.addColorStop(0, "rgba(0,0,0,0.3)");
|
|
21510
|
+
sg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21511
|
+
ctx.fillStyle = sg;
|
|
21512
|
+
ctx.fillRect(spine + backW, by, Math.min(shadowW, bx + pW * 2 - spine - backW), pH);
|
|
21513
|
+
}
|
|
21514
|
+
const cx = spine + backW;
|
|
21515
|
+
this._drawCrease(ctx, cx, by, pH, "right");
|
|
21516
|
+
}
|
|
21517
|
+
}
|
|
21518
|
+
this._drawSpineAndShadow(ctx, bx, by, pW, pH);
|
|
21519
|
+
}
|
|
21520
|
+
_drawCrease(ctx, cx, by, pH, highlightSide) {
|
|
21521
|
+
const w = 10;
|
|
21522
|
+
const g = ctx.createLinearGradient(cx - w, 0, cx + w, 0);
|
|
21523
|
+
if (highlightSide === "right") {
|
|
21524
|
+
g.addColorStop(0, "rgba(0,0,0,0)");
|
|
21525
|
+
g.addColorStop(0.35, "rgba(0,0,0,0.28)");
|
|
21526
|
+
g.addColorStop(0.55, "rgba(255,255,255,0.22)");
|
|
21527
|
+
g.addColorStop(1, "rgba(0,0,0,0)");
|
|
21418
21528
|
} else {
|
|
21419
|
-
|
|
21420
|
-
|
|
21421
|
-
|
|
21422
|
-
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21426
|
-
|
|
21427
|
-
|
|
21529
|
+
g.addColorStop(0, "rgba(0,0,0,0)");
|
|
21530
|
+
g.addColorStop(0.45, "rgba(255,255,255,0.22)");
|
|
21531
|
+
g.addColorStop(0.65, "rgba(0,0,0,0.28)");
|
|
21532
|
+
g.addColorStop(1, "rgba(0,0,0,0)");
|
|
21533
|
+
}
|
|
21534
|
+
ctx.fillStyle = g;
|
|
21535
|
+
ctx.fillRect(cx - w, by, w * 2, pH);
|
|
21536
|
+
}
|
|
21537
|
+
// ── Flip state ────────────────────────────────────────────────────────────
|
|
21538
|
+
_startFlip(dir, dest) {
|
|
21539
|
+
if (this._flip || !this._total) return;
|
|
21540
|
+
const target = dest !== void 0 ? dest : this._spread + dir * 2;
|
|
21541
|
+
if (target < 0 || target + 1 > this._total) return;
|
|
21542
|
+
this._opts.onFlip?.(this._spread + 1, target + 1);
|
|
21543
|
+
this._flip = { dir, src: this._spread, dest: target, progress: 0, start: null };
|
|
21544
|
+
const duration = this._opts.flippingTime;
|
|
21428
21545
|
const step = (ts) => {
|
|
21429
|
-
if (!
|
|
21430
|
-
|
|
21431
|
-
|
|
21432
|
-
|
|
21433
|
-
|
|
21434
|
-
|
|
21435
|
-
|
|
21436
|
-
shadow2.style.opacity = shadowPeak * shadowIntensity;
|
|
21437
|
-
if (progress < 1) {
|
|
21438
|
-
requestAnimationFrame(step);
|
|
21546
|
+
if (!this._flip) return;
|
|
21547
|
+
if (!this._flip.start) this._flip.start = ts;
|
|
21548
|
+
this._flip.progress = clamp((ts - this._flip.start) / duration, 0, 1);
|
|
21549
|
+
if (this._flip.progress >= 1) {
|
|
21550
|
+
this._spread = target;
|
|
21551
|
+
this._flip = null;
|
|
21552
|
+
this._updateUI();
|
|
21439
21553
|
} else {
|
|
21440
|
-
|
|
21441
|
-
shadow2.style.opacity = 0;
|
|
21442
|
-
this._spread = targetSpread;
|
|
21443
|
-
this._flipping = false;
|
|
21444
|
-
this._renderSpread(targetSpread, false);
|
|
21554
|
+
requestAnimationFrame(step);
|
|
21445
21555
|
}
|
|
21446
21556
|
};
|
|
21447
21557
|
requestAnimationFrame(step);
|
|
21448
21558
|
}
|
|
21449
21559
|
// ── Events ────────────────────────────────────────────────────────────────
|
|
21450
21560
|
_bindEvents() {
|
|
21451
|
-
|
|
21452
|
-
|
|
21453
|
-
|
|
21454
|
-
|
|
21561
|
+
this._el.querySelectorAll(".pfb__btn--prev, .pfb__tb-prev").forEach((b) => b.addEventListener("click", () => this.prev()));
|
|
21562
|
+
this._el.querySelectorAll(".pfb__btn--next, .pfb__tb-next").forEach((b) => b.addEventListener("click", () => this.next()));
|
|
21563
|
+
const fsBtn = this._el.querySelector(".pfb__btn--fs");
|
|
21564
|
+
if (fsBtn) {
|
|
21565
|
+
fsBtn.addEventListener("click", () => {
|
|
21566
|
+
if (!document.fullscreenElement) {
|
|
21567
|
+
this._el.requestFullscreen?.() || this._el.webkitRequestFullscreen?.();
|
|
21568
|
+
} else {
|
|
21569
|
+
document.exitFullscreen?.() || document.webkitExitFullscreen?.();
|
|
21570
|
+
}
|
|
21571
|
+
});
|
|
21572
|
+
}
|
|
21455
21573
|
this._onKey = (e) => {
|
|
21456
|
-
if (
|
|
21457
|
-
if (
|
|
21574
|
+
if (["ArrowRight", "ArrowDown"].includes(e.key)) this.next();
|
|
21575
|
+
if (["ArrowLeft", "ArrowUp"].includes(e.key)) this.prev();
|
|
21458
21576
|
};
|
|
21459
21577
|
window.addEventListener("keydown", this._onKey);
|
|
21460
|
-
|
|
21461
|
-
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
|
|
21465
|
-
|
|
21466
|
-
|
|
21467
|
-
|
|
21578
|
+
this._canvas.addEventListener("click", (e) => {
|
|
21579
|
+
if (this._flip) return;
|
|
21580
|
+
const rect = this._canvas.getBoundingClientRect();
|
|
21581
|
+
if (e.clientX - rect.left < this._canvas.width / 2) this.prev();
|
|
21582
|
+
else this.next();
|
|
21583
|
+
});
|
|
21584
|
+
let tx = 0;
|
|
21585
|
+
this._canvas.addEventListener("touchstart", (e) => {
|
|
21586
|
+
tx = e.touches[0].clientX;
|
|
21468
21587
|
}, { passive: true });
|
|
21469
|
-
this.
|
|
21470
|
-
const dx = e.changedTouches[0].clientX -
|
|
21588
|
+
this._canvas.addEventListener("touchend", (e) => {
|
|
21589
|
+
const dx = e.changedTouches[0].clientX - tx;
|
|
21471
21590
|
if (Math.abs(dx) > 40) dx < 0 ? this.next() : this.prev();
|
|
21472
21591
|
}, { passive: true });
|
|
21592
|
+
this._onResize = () => this._resizeCanvas();
|
|
21593
|
+
window.addEventListener("resize", this._onResize);
|
|
21473
21594
|
}
|
|
21474
|
-
// ── UI Helpers ────────────────────────────────────────────────────────────
|
|
21475
21595
|
_updateUI() {
|
|
21476
|
-
|
|
21477
|
-
const
|
|
21478
|
-
|
|
21479
|
-
|
|
21480
|
-
this._pageInfo.textContent = `${leftPage} \u2013 ${rightPage} / ${this._total}`;
|
|
21481
|
-
} else {
|
|
21482
|
-
this._pageInfo.textContent = `${leftPage} / ${this._total}`;
|
|
21596
|
+
const i = this._spread;
|
|
21597
|
+
const r = i + 2;
|
|
21598
|
+
if (this._pgInfo) {
|
|
21599
|
+
this._pgInfo.textContent = r <= this._total ? `${i + 1} \u2013 ${r} / ${this._total}` : `${i + 1} / ${this._total}`;
|
|
21483
21600
|
}
|
|
21484
|
-
|
|
21485
|
-
|
|
21486
|
-
prevBtns.forEach((b) => {
|
|
21487
|
-
b.disabled = this._spread === 0;
|
|
21601
|
+
this._el.querySelectorAll(".pfb__btn--prev, .pfb__tb-prev").forEach((b) => {
|
|
21602
|
+
b.disabled = i <= 0;
|
|
21488
21603
|
});
|
|
21489
|
-
|
|
21490
|
-
b.disabled =
|
|
21604
|
+
this._el.querySelectorAll(".pfb__btn--next, .pfb__tb-next").forEach((b) => {
|
|
21605
|
+
b.disabled = i + 2 >= this._total;
|
|
21491
21606
|
});
|
|
21492
21607
|
}
|
|
21493
21608
|
_showError(msg) {
|
|
21494
21609
|
this._loader.hidden = true;
|
|
21495
21610
|
this._errorEl.hidden = false;
|
|
21496
|
-
this._errorEl.textContent = `\u26A0\uFE0F
|
|
21611
|
+
this._errorEl.textContent = `\u26A0\uFE0F ${msg}`;
|
|
21497
21612
|
}
|
|
21498
21613
|
};
|
|
21499
21614
|
function createFlipbook(container, pdfUrl, options) {
|