pdf-flipbook 1.2.0 → 1.3.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.esm.js +203 -243
- package/dist/pdf-flipbook.esm.js.map +3 -3
- package/dist/pdf-flipbook.esm.min.js +30 -14
- package/dist/pdf-flipbook.esm.min.js.map +3 -3
- package/dist/pdf-flipbook.umd.js +203 -243
- package/dist/pdf-flipbook.umd.js.map +3 -3
- package/package.json +2 -2
package/dist/pdf-flipbook.umd.js
CHANGED
|
@@ -21238,331 +21238,291 @@ var PdfFlipbook = (() => {
|
|
|
21238
21238
|
__webpack_exports__GlobalWorkerOptions.workerSrc = `https://cdnjs.cloudflare.com/ajax/libs/pdf.js/${__webpack_exports__version}/pdf.worker.min.mjs`;
|
|
21239
21239
|
}
|
|
21240
21240
|
var DEFAULTS = {
|
|
21241
|
-
|
|
21242
|
-
|
|
21241
|
+
/** Flip animation duration in ms */
|
|
21242
|
+
flipDuration: 700,
|
|
21243
|
+
/** Shadow opacity at peak of flip (0-1) */
|
|
21244
|
+
shadowIntensity: 0.4,
|
|
21245
|
+
/** Device-pixel-ratio cap for canvas rendering */
|
|
21243
21246
|
maxDpr: 2,
|
|
21247
|
+
/** Whether to show the toolbar */
|
|
21244
21248
|
toolbar: true,
|
|
21249
|
+
/** Toolbar theme: "light" | "dark" */
|
|
21245
21250
|
theme: "dark",
|
|
21251
|
+
/** Preload N pages ahead */
|
|
21252
|
+
preloadAhead: 2,
|
|
21253
|
+
/** Called when a page turn starts: (fromPage, toPage) */
|
|
21246
21254
|
onFlip: null,
|
|
21255
|
+
/** Called when all pages are loaded */
|
|
21247
21256
|
onReady: null,
|
|
21257
|
+
/** Called on load error */
|
|
21248
21258
|
onError: null
|
|
21249
21259
|
};
|
|
21250
21260
|
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
21251
|
-
var
|
|
21252
|
-
var
|
|
21261
|
+
var even = (n) => n % 2 === 0 ? n : n - 1;
|
|
21262
|
+
var isTouchDevice = () => "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
21253
21263
|
var PdfFlipbook = class {
|
|
21264
|
+
/**
|
|
21265
|
+
* @param {string|HTMLElement} container CSS selector or DOM element
|
|
21266
|
+
* @param {string} pdfUrl Remote or local PDF URL
|
|
21267
|
+
* @param {object} [options] See DEFAULTS
|
|
21268
|
+
*/
|
|
21254
21269
|
constructor(container, pdfUrl, options = {}) {
|
|
21255
21270
|
if (!pdfUrl) throw new Error("[PdfFlipbook] pdfUrl is required");
|
|
21256
21271
|
this._el = typeof container === "string" ? document.querySelector(container) : container;
|
|
21257
|
-
if (!this._el) throw new Error(
|
|
21272
|
+
if (!this._el) throw new Error(`[PdfFlipbook] container not found: ${container}`);
|
|
21258
21273
|
this._url = pdfUrl;
|
|
21259
21274
|
this._opts = { ...DEFAULTS, ...options };
|
|
21260
21275
|
this._pages = [];
|
|
21261
|
-
this.
|
|
21276
|
+
this._pdf = null;
|
|
21262
21277
|
this._total = 0;
|
|
21278
|
+
this._spread = 0;
|
|
21263
21279
|
this._flipping = false;
|
|
21264
|
-
this._animId = null;
|
|
21265
|
-
this._pageAspect = 1.414;
|
|
21266
21280
|
this._init();
|
|
21267
21281
|
}
|
|
21282
|
+
// ── Static workerSrc override ──────────────────────────────────────────────
|
|
21268
21283
|
static set workerSrc(src) {
|
|
21269
21284
|
__webpack_exports__GlobalWorkerOptions.workerSrc = src;
|
|
21270
21285
|
}
|
|
21286
|
+
// ═══ PUBLIC API ═══════════════════════════════════════════════════════════
|
|
21287
|
+
/** Go to the next spread */
|
|
21271
21288
|
next() {
|
|
21272
|
-
this.
|
|
21289
|
+
this._flip(1);
|
|
21273
21290
|
}
|
|
21291
|
+
/** Go to the previous spread */
|
|
21274
21292
|
prev() {
|
|
21275
|
-
this.
|
|
21293
|
+
this._flip(-1);
|
|
21276
21294
|
}
|
|
21277
|
-
|
|
21278
|
-
|
|
21279
|
-
const target =
|
|
21280
|
-
|
|
21295
|
+
/** Jump directly to a page (1-indexed) */
|
|
21296
|
+
goTo(pageNum) {
|
|
21297
|
+
const target = clamp(pageNum - 1, 0, this._total - 1);
|
|
21298
|
+
const targetSpread = even(target);
|
|
21299
|
+
if (targetSpread === this._spread) return;
|
|
21300
|
+
const dir = targetSpread > this._spread ? 1 : -1;
|
|
21301
|
+
this._animateFlip(dir, targetSpread);
|
|
21281
21302
|
}
|
|
21303
|
+
/** Destroy the flipbook and clean up */
|
|
21282
21304
|
destroy() {
|
|
21283
|
-
|
|
21305
|
+
this._el.innerHTML = "";
|
|
21306
|
+
this._el.classList.remove("pfb", `pfb--${this._opts.theme}`);
|
|
21307
|
+
this._pages = [];
|
|
21284
21308
|
window.removeEventListener("resize", this._onResize);
|
|
21285
21309
|
window.removeEventListener("keydown", this._onKey);
|
|
21286
|
-
this._el.innerHTML = "";
|
|
21287
21310
|
}
|
|
21311
|
+
// ═══ PRIVATE ══════════════════════════════════════════════════════════════
|
|
21288
21312
|
async _init() {
|
|
21289
21313
|
this._buildShell();
|
|
21290
21314
|
try {
|
|
21291
21315
|
await this._loadPdf();
|
|
21292
21316
|
this._bindEvents();
|
|
21293
21317
|
this._opts.onReady?.();
|
|
21294
|
-
} catch (
|
|
21295
|
-
console.error("[PdfFlipbook]",
|
|
21296
|
-
this._showError(
|
|
21297
|
-
this._opts.onError?.(
|
|
21318
|
+
} catch (err) {
|
|
21319
|
+
console.error("[PdfFlipbook]", err);
|
|
21320
|
+
this._showError(err.message);
|
|
21321
|
+
this._opts.onError?.(err);
|
|
21298
21322
|
}
|
|
21299
21323
|
}
|
|
21324
|
+
// ── DOM Shell ──────────────────────────────────────────────────────────────
|
|
21300
21325
|
_buildShell() {
|
|
21301
21326
|
this._el.classList.add("pfb", `pfb--${this._opts.theme}`);
|
|
21302
21327
|
this._el.innerHTML = `
|
|
21303
21328
|
<div class="pfb__stage">
|
|
21304
|
-
<
|
|
21305
|
-
|
|
21306
|
-
|
|
21307
|
-
|
|
21329
|
+
<div class="pfb__book">
|
|
21330
|
+
<div class="pfb__page pfb__page--left">
|
|
21331
|
+
<div class="pfb__page-front"></div>
|
|
21332
|
+
<div class="pfb__page-back"></div>
|
|
21333
|
+
<div class="pfb__shadow pfb__shadow--left"></div>
|
|
21334
|
+
</div>
|
|
21335
|
+
<div class="pfb__spine"></div>
|
|
21336
|
+
<div class="pfb__page pfb__page--right">
|
|
21337
|
+
<div class="pfb__page-front"></div>
|
|
21338
|
+
<div class="pfb__page-back"></div>
|
|
21339
|
+
<div class="pfb__shadow pfb__shadow--right"></div>
|
|
21340
|
+
</div>
|
|
21341
|
+
</div>
|
|
21342
|
+
<button class="pfb__btn pfb__btn--prev" aria-label="Previous page">←</button>
|
|
21343
|
+
<button class="pfb__btn pfb__btn--next" aria-label="Next page">→</button>
|
|
21344
|
+
<div class="pfb__loader">
|
|
21345
|
+
<div class="pfb__spinner"></div>
|
|
21346
|
+
<span class="pfb__loader-text">Loading PDF\u2026</span>
|
|
21347
|
+
</div>
|
|
21308
21348
|
<div class="pfb__error" hidden></div>
|
|
21309
21349
|
</div>
|
|
21310
|
-
${this._opts.toolbar ?
|
|
21311
|
-
|
|
21350
|
+
${this._opts.toolbar ? `
|
|
21351
|
+
<div class="pfb__toolbar">
|
|
21352
|
+
<button class="pfb__tb-btn pfb__tb-prev" aria-label="Previous">←</button>
|
|
21312
21353
|
<span class="pfb__page-info"></span>
|
|
21313
|
-
<button class="pfb__tb-btn pfb__tb-next">→</button>
|
|
21354
|
+
<button class="pfb__tb-btn pfb__tb-next" aria-label="Next">→</button>
|
|
21314
21355
|
</div>` : ""}
|
|
21315
21356
|
`;
|
|
21316
21357
|
this._stage = this._el.querySelector(".pfb__stage");
|
|
21317
|
-
this.
|
|
21318
|
-
this._ctx = this._canvas.getContext("2d");
|
|
21358
|
+
this._book = this._el.querySelector(".pfb__book");
|
|
21319
21359
|
this._loader = this._el.querySelector(".pfb__loader");
|
|
21320
|
-
this._loaderTx = this._el.querySelector(".pfb__loader-text");
|
|
21321
21360
|
this._errorEl = this._el.querySelector(".pfb__error");
|
|
21322
21361
|
this._pageInfo = this._el.querySelector(".pfb__page-info");
|
|
21323
|
-
this.
|
|
21324
|
-
|
|
21325
|
-
|
|
21326
|
-
|
|
21327
|
-
|
|
21328
|
-
this.
|
|
21329
|
-
|
|
21330
|
-
|
|
21331
|
-
this._canvas.style.height = H + "px";
|
|
21332
|
-
if (this._total) this._drawSpread();
|
|
21333
|
-
}
|
|
21362
|
+
this._leftFront = this._el.querySelector(".pfb__page--left .pfb__page-front");
|
|
21363
|
+
this._leftBack = this._el.querySelector(".pfb__page--left .pfb__page-back");
|
|
21364
|
+
this._leftShadow = this._el.querySelector(".pfb__shadow--left");
|
|
21365
|
+
this._rightFront = this._el.querySelector(".pfb__page--right .pfb__page-front");
|
|
21366
|
+
this._rightBack = this._el.querySelector(".pfb__page--right .pfb__page-back");
|
|
21367
|
+
this._rightShadow = this._el.querySelector(".pfb__shadow--right");
|
|
21368
|
+
}
|
|
21369
|
+
// ── PDF Loading ────────────────────────────────────────────────────────────
|
|
21334
21370
|
async _loadPdf() {
|
|
21335
|
-
const
|
|
21336
|
-
this.
|
|
21337
|
-
|
|
21338
|
-
|
|
21339
|
-
this._pageAspect = vp0.height / vp0.width;
|
|
21340
|
-
for (let i = 1; i <= this._total; i++) {
|
|
21341
|
-
if (this._loaderTx) this._loaderTx.textContent = `Loading ${i} / ${this._total}\u2026`;
|
|
21342
|
-
const page = await pdf.getPage(i);
|
|
21343
|
-
const dpr = clamp(window.devicePixelRatio || 1, 1, this._opts.maxDpr);
|
|
21344
|
-
const rW = 1200 * dpr;
|
|
21345
|
-
const vp = page.getViewport({ scale: rW / vp0.width });
|
|
21346
|
-
const cvs = document.createElement("canvas");
|
|
21347
|
-
cvs.width = vp.width;
|
|
21348
|
-
cvs.height = vp.height;
|
|
21349
|
-
await page.render({ canvasContext: cvs.getContext("2d"), viewport: vp }).promise;
|
|
21350
|
-
this._pages.push(cvs);
|
|
21351
|
-
}
|
|
21371
|
+
const loadingTask = __webpack_exports__getDocument({ url: this._url, withCredentials: false });
|
|
21372
|
+
this._pdf = await loadingTask.promise;
|
|
21373
|
+
this._total = this._pdf.numPages;
|
|
21374
|
+
await this._prerenderAll();
|
|
21352
21375
|
this._loader.hidden = true;
|
|
21353
|
-
this.
|
|
21376
|
+
this._renderSpread(0, false);
|
|
21377
|
+
}
|
|
21378
|
+
async _prerenderAll() {
|
|
21379
|
+
this._loaderText = this._el.querySelector(".pfb__loader-text");
|
|
21380
|
+
for (let i = 1; i <= this._total; i++) {
|
|
21381
|
+
const canvas = await this._renderPage(i);
|
|
21382
|
+
this._pages.push(canvas);
|
|
21383
|
+
if (this._loaderText) {
|
|
21384
|
+
this._loaderText.textContent = `Loading ${i} / ${this._total}\u2026`;
|
|
21385
|
+
}
|
|
21386
|
+
}
|
|
21387
|
+
}
|
|
21388
|
+
async _renderPage(pageNum) {
|
|
21389
|
+
const page = await this._pdf.getPage(pageNum);
|
|
21390
|
+
const dpr = clamp(window.devicePixelRatio || 1, 1, this._opts.maxDpr);
|
|
21391
|
+
const viewport = page.getViewport({ scale: 1 });
|
|
21392
|
+
const baseW = 900;
|
|
21393
|
+
const scale = baseW / viewport.width;
|
|
21394
|
+
const vp = page.getViewport({ scale: scale * dpr });
|
|
21395
|
+
const canvas = document.createElement("canvas");
|
|
21396
|
+
canvas.width = vp.width;
|
|
21397
|
+
canvas.height = vp.height;
|
|
21398
|
+
canvas.style.width = `${vp.width / dpr}px`;
|
|
21399
|
+
canvas.style.height = `${vp.height / dpr}px`;
|
|
21400
|
+
canvas.dataset.pageNum = pageNum;
|
|
21401
|
+
const ctx = canvas.getContext("2d");
|
|
21402
|
+
await page.render({ canvasContext: ctx, viewport: vp }).promise;
|
|
21403
|
+
return canvas;
|
|
21404
|
+
}
|
|
21405
|
+
// ── Spread Rendering ───────────────────────────────────────────────────────
|
|
21406
|
+
_renderSpread(spreadIndex, animate) {
|
|
21407
|
+
this._spread = clamp(spreadIndex, 0, even(this._total - 1) || 0);
|
|
21408
|
+
const leftIdx = this._spread;
|
|
21409
|
+
const rightIdx = this._spread + 1;
|
|
21410
|
+
this._setFace(this._leftFront, leftIdx);
|
|
21411
|
+
this._setFace(this._rightFront, rightIdx);
|
|
21412
|
+
this._setFace(this._leftBack, null);
|
|
21413
|
+
this._setFace(this._rightBack, null);
|
|
21354
21414
|
this._updateUI();
|
|
21355
21415
|
}
|
|
21356
|
-
|
|
21357
|
-
|
|
21358
|
-
|
|
21359
|
-
|
|
21360
|
-
|
|
21361
|
-
|
|
21362
|
-
|
|
21363
|
-
|
|
21364
|
-
|
|
21365
|
-
|
|
21366
|
-
|
|
21367
|
-
|
|
21368
|
-
bH = bW / 2 * this._pageAspect;
|
|
21369
|
-
}
|
|
21370
|
-
}
|
|
21371
|
-
const x = (cW - bW) / 2, y = (cH - bH) / 2;
|
|
21372
|
-
return { x, y, w: bW, h: bH, mobile, pageW: mobile ? bW : bW / 2, pageH: bH };
|
|
21373
|
-
}
|
|
21374
|
-
_drawPage(ctx, canvas, px, py, pw, ph) {
|
|
21375
|
-
ctx.fillStyle = "#fdfaf5";
|
|
21376
|
-
ctx.fillRect(px, py, pw, ph);
|
|
21377
|
-
if (canvas) ctx.drawImage(canvas, px, py, pw, ph);
|
|
21378
|
-
ctx.strokeStyle = "rgba(0,0,0,0.07)";
|
|
21379
|
-
ctx.lineWidth = 1;
|
|
21380
|
-
ctx.strokeRect(px + 0.5, py + 0.5, pw - 1, ph - 1);
|
|
21381
|
-
}
|
|
21382
|
-
_drawSpine(ctx, sx, sy, sh) {
|
|
21383
|
-
const g = ctx.createLinearGradient(sx - 10, 0, sx + 10, 0);
|
|
21384
|
-
g.addColorStop(0, "rgba(0,0,0,0.2)");
|
|
21385
|
-
g.addColorStop(0.45, "rgba(0,0,0,0.5)");
|
|
21386
|
-
g.addColorStop(0.55, "rgba(0,0,0,0.5)");
|
|
21387
|
-
g.addColorStop(1, "rgba(0,0,0,0.08)");
|
|
21388
|
-
ctx.fillStyle = g;
|
|
21389
|
-
ctx.fillRect(sx - 10, sy, 20, sh);
|
|
21390
|
-
}
|
|
21391
|
-
_drawBookShadow(bx, by, bw, bh) {
|
|
21392
|
-
const ctx = this._ctx;
|
|
21393
|
-
const g = ctx.createLinearGradient(0, by + bh, 0, by + bh + 40);
|
|
21394
|
-
g.addColorStop(0, "rgba(0,0,0,0.35)");
|
|
21395
|
-
g.addColorStop(1, "rgba(0,0,0,0)");
|
|
21396
|
-
ctx.fillStyle = g;
|
|
21397
|
-
ctx.fillRect(bx, by + bh - 4, bw, 44);
|
|
21398
|
-
}
|
|
21399
|
-
_drawSpread(flip) {
|
|
21400
|
-
const ctx = this._ctx;
|
|
21401
|
-
const { x, y, w, h, mobile, pageW, pageH } = this._bookRect();
|
|
21402
|
-
ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
|
|
21403
|
-
this._drawBookShadow(x, y, w, h);
|
|
21404
|
-
if (mobile) {
|
|
21405
|
-
this._drawPage(ctx, this._pages[this._spread] || null, x, y, pageW, pageH);
|
|
21406
|
-
} else {
|
|
21407
|
-
this._drawPage(ctx, this._pages[this._spread] || null, x, y, pageW, pageH);
|
|
21408
|
-
this._drawPage(ctx, this._pages[this._spread + 1] || null, x + pageW, y, pageW, pageH);
|
|
21409
|
-
this._drawSpine(ctx, x + pageW, y, pageH);
|
|
21416
|
+
_setFace(face, pageIndex) {
|
|
21417
|
+
face.innerHTML = "";
|
|
21418
|
+
if (pageIndex === null || pageIndex >= this._total) {
|
|
21419
|
+
face.classList.add("pfb__face--blank");
|
|
21420
|
+
return;
|
|
21421
|
+
}
|
|
21422
|
+
face.classList.remove("pfb__face--blank");
|
|
21423
|
+
const canvas = this._pages[pageIndex];
|
|
21424
|
+
if (canvas) {
|
|
21425
|
+
const clone = canvas.cloneNode();
|
|
21426
|
+
clone.getContext("2d").drawImage(canvas, 0, 0);
|
|
21427
|
+
face.appendChild(clone);
|
|
21410
21428
|
}
|
|
21411
|
-
if (flip) this._renderFlip(ctx, flip, x, y, pageW, pageH, mobile);
|
|
21412
21429
|
}
|
|
21413
|
-
|
|
21414
|
-
|
|
21415
|
-
|
|
21430
|
+
// ── Flip Animation ────────────────────────────────────────────────────────
|
|
21431
|
+
_flip(dir) {
|
|
21432
|
+
if (this._flipping) return;
|
|
21433
|
+
const next = this._spread + dir * 2;
|
|
21434
|
+
if (next < 0 || next > even(this._total - 1)) return;
|
|
21435
|
+
this._animateFlip(dir, next);
|
|
21436
|
+
}
|
|
21437
|
+
_animateFlip(dir, targetSpread) {
|
|
21438
|
+
if (this._flipping) return;
|
|
21439
|
+
this._flipping = true;
|
|
21440
|
+
const { flipDuration, shadowIntensity, onFlip } = this._opts;
|
|
21441
|
+
const currentLeft = this._spread;
|
|
21442
|
+
const currentRight = this._spread + 1;
|
|
21443
|
+
onFlip?.(currentLeft + 1, targetSpread + 1);
|
|
21416
21444
|
if (dir === 1) {
|
|
21417
|
-
|
|
21418
|
-
|
|
21419
|
-
this._drawPage(ctx, this._pages[sp + 2] || null, bx, by, pageW, pageH);
|
|
21420
|
-
this._drawPage(ctx, this._pages[sp + 3] || null, bx + pageW, by, pageW, pageH);
|
|
21421
|
-
if (foldX > bx + pageW) {
|
|
21422
|
-
ctx.save();
|
|
21423
|
-
ctx.beginPath();
|
|
21424
|
-
ctx.rect(bx + pageW, by, foldX - (bx + pageW), pageH);
|
|
21425
|
-
ctx.clip();
|
|
21426
|
-
this._drawPage(ctx, this._pages[sp + 1] || null, bx + pageW, by, pageW, pageH);
|
|
21427
|
-
ctx.restore();
|
|
21428
|
-
}
|
|
21429
|
-
if (turnW > 1) {
|
|
21430
|
-
ctx.save();
|
|
21431
|
-
ctx.beginPath();
|
|
21432
|
-
ctx.rect(foldX, by, turnW, pageH);
|
|
21433
|
-
ctx.clip();
|
|
21434
|
-
const sc = Math.max(0.82, 1 - t * 0.18);
|
|
21435
|
-
ctx.translate(foldX + turnW, by);
|
|
21436
|
-
ctx.scale(-sc, 1);
|
|
21437
|
-
ctx.drawImage(this._pages[sp + 2] || this._pages[sp + 1], 0, 0, turnW / sc, pageH);
|
|
21438
|
-
const lg = ctx.createLinearGradient(0, 0, turnW / sc, 0);
|
|
21439
|
-
lg.addColorStop(0, `rgba(255,255,255,${0.08 * (1 - t)})`);
|
|
21440
|
-
lg.addColorStop(1, `rgba(0,0,0,${0.12 * t})`);
|
|
21441
|
-
ctx.fillStyle = lg;
|
|
21442
|
-
ctx.fillRect(0, 0, turnW / sc, pageH);
|
|
21443
|
-
ctx.restore();
|
|
21444
|
-
}
|
|
21445
|
-
const fg = ctx.createLinearGradient(foldX - 28, 0, foldX + 8, 0);
|
|
21446
|
-
fg.addColorStop(0, "rgba(0,0,0,0)");
|
|
21447
|
-
fg.addColorStop(0.55, `rgba(0,0,0,${si * 0.55})`);
|
|
21448
|
-
fg.addColorStop(0.85, `rgba(0,0,0,${si})`);
|
|
21449
|
-
fg.addColorStop(1, "rgba(255,255,255,0.15)");
|
|
21450
|
-
ctx.fillStyle = fg;
|
|
21451
|
-
ctx.fillRect(foldX - 28, by, 36, pageH);
|
|
21452
|
-
const cg = ctx.createLinearGradient(bx + pageW, 0, bx + pageW - 50, 0);
|
|
21453
|
-
cg.addColorStop(0, `rgba(0,0,0,${si * 0.4 * (1 - t)})`);
|
|
21454
|
-
cg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21455
|
-
ctx.fillStyle = cg;
|
|
21456
|
-
ctx.fillRect(bx + pageW - 50, by, 50, pageH);
|
|
21445
|
+
this._setFace(this._rightBack, targetSpread + 1);
|
|
21446
|
+
this._setFace(this._leftBack, targetSpread);
|
|
21457
21447
|
} else {
|
|
21458
|
-
|
|
21459
|
-
|
|
21460
|
-
|
|
21461
|
-
|
|
21462
|
-
|
|
21463
|
-
|
|
21464
|
-
|
|
21465
|
-
|
|
21466
|
-
|
|
21467
|
-
this._drawPage(ctx, this._pages[sp] || null, bx, by, pageW, pageH);
|
|
21468
|
-
ctx.restore();
|
|
21469
|
-
}
|
|
21470
|
-
if (turnW > 1) {
|
|
21471
|
-
ctx.save();
|
|
21472
|
-
ctx.beginPath();
|
|
21473
|
-
ctx.rect(bx, by, turnW, pageH);
|
|
21474
|
-
ctx.clip();
|
|
21475
|
-
const sc = Math.max(0.82, 1 - t * 0.18);
|
|
21476
|
-
ctx.translate(bx + turnW, by);
|
|
21477
|
-
ctx.scale(-sc, 1);
|
|
21478
|
-
ctx.drawImage(this._pages[sp - 2] || this._pages[sp], 0, 0, turnW / sc, pageH);
|
|
21479
|
-
const lg = ctx.createLinearGradient(turnW / sc, 0, 0, 0);
|
|
21480
|
-
lg.addColorStop(0, `rgba(255,255,255,${0.08 * (1 - t)})`);
|
|
21481
|
-
lg.addColorStop(1, `rgba(0,0,0,${0.12 * t})`);
|
|
21482
|
-
ctx.fillStyle = lg;
|
|
21483
|
-
ctx.fillRect(0, 0, turnW / sc, pageH);
|
|
21484
|
-
ctx.restore();
|
|
21485
|
-
}
|
|
21486
|
-
const fg = ctx.createLinearGradient(foldX - 8, 0, foldX + 28, 0);
|
|
21487
|
-
fg.addColorStop(0, "rgba(255,255,255,0.15)");
|
|
21488
|
-
fg.addColorStop(0.15, `rgba(0,0,0,${si})`);
|
|
21489
|
-
fg.addColorStop(0.45, `rgba(0,0,0,${si * 0.55})`);
|
|
21490
|
-
fg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21491
|
-
ctx.fillStyle = fg;
|
|
21492
|
-
ctx.fillRect(foldX - 8, by, 36, pageH);
|
|
21493
|
-
const cg = ctx.createLinearGradient(bx + pageW, 0, bx + pageW + 50, 0);
|
|
21494
|
-
cg.addColorStop(0, `rgba(0,0,0,${si * 0.4 * (1 - t)})`);
|
|
21495
|
-
cg.addColorStop(1, "rgba(0,0,0,0)");
|
|
21496
|
-
ctx.fillStyle = cg;
|
|
21497
|
-
ctx.fillRect(bx + pageW, by, 50, pageH);
|
|
21498
|
-
}
|
|
21499
|
-
this._drawSpine(ctx, bx + pageW, by, pageH);
|
|
21500
|
-
}
|
|
21501
|
-
_startFlip(dir, targetSpread) {
|
|
21502
|
-
if (this._flipping || !this._total) return;
|
|
21503
|
-
const next = targetSpread !== void 0 ? targetSpread : this._spread + dir * 2;
|
|
21504
|
-
if (next < 0 || next + 1 > this._total) return;
|
|
21505
|
-
this._flipping = true;
|
|
21506
|
-
this._opts.onFlip?.(this._spread + 1, next + 1);
|
|
21507
|
-
const duration = this._opts.flipDuration;
|
|
21508
|
-
let start = null;
|
|
21448
|
+
this._setFace(this._leftBack, targetSpread);
|
|
21449
|
+
this._setFace(this._rightBack, targetSpread + 1);
|
|
21450
|
+
}
|
|
21451
|
+
const flippingPage = dir === 1 ? this._el.querySelector(".pfb__page--right") : this._el.querySelector(".pfb__page--left");
|
|
21452
|
+
const shadow2 = dir === 1 ? this._rightShadow : this._leftShadow;
|
|
21453
|
+
const startAngle = 0;
|
|
21454
|
+
const endAngle = dir === 1 ? -180 : 180;
|
|
21455
|
+
const easeInOut = (t) => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
|
|
21456
|
+
let startTime = null;
|
|
21509
21457
|
const step = (ts) => {
|
|
21510
|
-
if (!
|
|
21511
|
-
const
|
|
21512
|
-
const
|
|
21513
|
-
|
|
21514
|
-
|
|
21515
|
-
|
|
21458
|
+
if (!startTime) startTime = ts;
|
|
21459
|
+
const elapsed = ts - startTime;
|
|
21460
|
+
const progress = clamp(elapsed / flipDuration, 0, 1);
|
|
21461
|
+
const eased = easeInOut(progress);
|
|
21462
|
+
const angle = startAngle + (endAngle - startAngle) * eased;
|
|
21463
|
+
flippingPage.style.transform = `rotateY(${angle}deg)`;
|
|
21464
|
+
const shadowPeak = Math.sin(progress * Math.PI);
|
|
21465
|
+
shadow2.style.opacity = shadowPeak * shadowIntensity;
|
|
21466
|
+
if (progress < 1) {
|
|
21467
|
+
requestAnimationFrame(step);
|
|
21516
21468
|
} else {
|
|
21517
|
-
|
|
21469
|
+
flippingPage.style.transform = "";
|
|
21470
|
+
shadow2.style.opacity = 0;
|
|
21471
|
+
this._spread = targetSpread;
|
|
21518
21472
|
this._flipping = false;
|
|
21519
|
-
this.
|
|
21520
|
-
this._updateUI();
|
|
21473
|
+
this._renderSpread(targetSpread, false);
|
|
21521
21474
|
}
|
|
21522
21475
|
};
|
|
21523
|
-
|
|
21476
|
+
requestAnimationFrame(step);
|
|
21524
21477
|
}
|
|
21478
|
+
// ── Events ────────────────────────────────────────────────────────────────
|
|
21525
21479
|
_bindEvents() {
|
|
21526
|
-
this._el.querySelectorAll(".pfb__btn--prev, .pfb__tb-prev")
|
|
21527
|
-
this._el.querySelectorAll(".pfb__btn--next, .pfb__tb-next")
|
|
21480
|
+
const prevBtns = this._el.querySelectorAll(".pfb__btn--prev, .pfb__tb-prev");
|
|
21481
|
+
const nextBtns = this._el.querySelectorAll(".pfb__btn--next, .pfb__tb-next");
|
|
21482
|
+
prevBtns.forEach((b) => b.addEventListener("click", () => this.prev()));
|
|
21483
|
+
nextBtns.forEach((b) => b.addEventListener("click", () => this.next()));
|
|
21528
21484
|
this._onKey = (e) => {
|
|
21529
|
-
if (
|
|
21530
|
-
if (
|
|
21485
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") this.next();
|
|
21486
|
+
if (e.key === "ArrowLeft" || e.key === "ArrowUp") this.prev();
|
|
21531
21487
|
};
|
|
21532
21488
|
window.addEventListener("keydown", this._onKey);
|
|
21533
|
-
this.
|
|
21534
|
-
|
|
21535
|
-
|
|
21536
|
-
|
|
21537
|
-
|
|
21538
|
-
|
|
21539
|
-
|
|
21540
|
-
|
|
21541
|
-
tx0 = e.touches[0].clientX;
|
|
21489
|
+
if (isTouchDevice()) this._bindSwipe();
|
|
21490
|
+
this._onResize = () => this._updateUI();
|
|
21491
|
+
window.addEventListener("resize", this._onResize);
|
|
21492
|
+
}
|
|
21493
|
+
_bindSwipe() {
|
|
21494
|
+
let startX = 0;
|
|
21495
|
+
this._stage.addEventListener("touchstart", (e) => {
|
|
21496
|
+
startX = e.touches[0].clientX;
|
|
21542
21497
|
}, { passive: true });
|
|
21543
|
-
this.
|
|
21544
|
-
const dx = e.changedTouches[0].clientX -
|
|
21498
|
+
this._stage.addEventListener("touchend", (e) => {
|
|
21499
|
+
const dx = e.changedTouches[0].clientX - startX;
|
|
21545
21500
|
if (Math.abs(dx) > 40) dx < 0 ? this.next() : this.prev();
|
|
21546
21501
|
}, { passive: true });
|
|
21547
|
-
this._onResize = () => this._resizeCanvas();
|
|
21548
|
-
window.addEventListener("resize", this._onResize);
|
|
21549
21502
|
}
|
|
21503
|
+
// ── UI Helpers ────────────────────────────────────────────────────────────
|
|
21550
21504
|
_updateUI() {
|
|
21551
|
-
this.
|
|
21552
|
-
|
|
21505
|
+
if (!this._pageInfo) return;
|
|
21506
|
+
const leftPage = this._spread + 1;
|
|
21507
|
+
const rightPage = this._spread + 2;
|
|
21508
|
+
if (rightPage <= this._total) {
|
|
21509
|
+
this._pageInfo.textContent = `${leftPage} \u2013 ${rightPage} / ${this._total}`;
|
|
21510
|
+
} else {
|
|
21511
|
+
this._pageInfo.textContent = `${leftPage} / ${this._total}`;
|
|
21512
|
+
}
|
|
21513
|
+
const prevBtns = this._el.querySelectorAll(".pfb__btn--prev, .pfb__tb-prev");
|
|
21514
|
+
const nextBtns = this._el.querySelectorAll(".pfb__btn--next, .pfb__tb-next");
|
|
21515
|
+
prevBtns.forEach((b) => {
|
|
21516
|
+
b.disabled = this._spread === 0;
|
|
21553
21517
|
});
|
|
21554
|
-
|
|
21518
|
+
nextBtns.forEach((b) => {
|
|
21555
21519
|
b.disabled = this._spread + 2 >= this._total;
|
|
21556
21520
|
});
|
|
21557
|
-
if (this._pageInfo) {
|
|
21558
|
-
const r = this._spread + 2;
|
|
21559
|
-
this._pageInfo.textContent = r <= this._total ? `${this._spread + 1} \u2013 ${r} / ${this._total}` : `${this._spread + 1} / ${this._total}`;
|
|
21560
|
-
}
|
|
21561
21521
|
}
|
|
21562
21522
|
_showError(msg) {
|
|
21563
21523
|
this._loader.hidden = true;
|
|
21564
21524
|
this._errorEl.hidden = false;
|
|
21565
|
-
this._errorEl.textContent = `\u26A0\uFE0F ${msg}`;
|
|
21525
|
+
this._errorEl.textContent = `\u26A0\uFE0F Could not load PDF: ${msg}`;
|
|
21566
21526
|
}
|
|
21567
21527
|
};
|
|
21568
21528
|
function createFlipbook(container, pdfUrl, options) {
|