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