hic-pageflip 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 +21 -0
- package/README.md +322 -0
- package/components/hic-pageflip-page.js +43 -0
- package/components/hic-pageflip.js +339 -0
- package/core/engines/engine-2d.js +470 -0
- package/core/engines/engine-3d.js +699 -0
- package/core/engines/engine-base.js +73 -0
- package/core/pageflip.js +657 -0
- package/core/utils/math.js +186 -0
- package/index.js +8 -0
- package/package.json +33 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <hic-pageflip> Custom Element Web Component.
|
|
3
|
+
* Uses Shadow DOM with <canvas layoutsubtree><slot></slot></canvas> to project
|
|
4
|
+
* light-DOM slide elements directly into the canvas without manual node manipulation.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Pageflip } from '../core/pageflip.js';
|
|
8
|
+
import './hic-pageflip-page.js';
|
|
9
|
+
|
|
10
|
+
const BaseElement = typeof HTMLElement !== 'undefined' ? HTMLElement : class {};
|
|
11
|
+
|
|
12
|
+
const templateHTML = `
|
|
13
|
+
<style>
|
|
14
|
+
:host {
|
|
15
|
+
display: block;
|
|
16
|
+
position: relative;
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: 100%;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
}
|
|
21
|
+
canvas {
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: 0;
|
|
24
|
+
left: 0;
|
|
25
|
+
width: 100%;
|
|
26
|
+
height: 100%;
|
|
27
|
+
display: block;
|
|
28
|
+
touch-action: none;
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
31
|
+
<canvas layoutsubtree>
|
|
32
|
+
<slot></slot>
|
|
33
|
+
</canvas>
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
export class HICPageflip extends BaseElement {
|
|
37
|
+
static get observedAttributes() {
|
|
38
|
+
return ['engine', 'page-width', 'page-height', 'page-background', 'page'];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
super();
|
|
43
|
+
|
|
44
|
+
if (typeof HTMLElement !== 'undefined' && this.attachShadow) {
|
|
45
|
+
this.attachShadow({ mode: 'open' });
|
|
46
|
+
const template = document.createElement('template');
|
|
47
|
+
template.innerHTML = templateHTML;
|
|
48
|
+
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
|
49
|
+
this.canvas = this.shadowRoot.querySelector('canvas');
|
|
50
|
+
this.slotElement = this.shadowRoot.querySelector('slot');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.slides = [];
|
|
54
|
+
this.pageflip = null;
|
|
55
|
+
this._engineMode = '2d';
|
|
56
|
+
this._resizeObserver = null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
connectedCallback() {
|
|
60
|
+
this.extractSlides();
|
|
61
|
+
this.setupPageflip();
|
|
62
|
+
|
|
63
|
+
if (this.slotElement) {
|
|
64
|
+
this._boundSlotChange = () => {
|
|
65
|
+
this.extractSlides();
|
|
66
|
+
if (this.pageflip) {
|
|
67
|
+
this.pageflip.slides = this.slides;
|
|
68
|
+
this.pageflip.totalPages = this.slides.length;
|
|
69
|
+
this.pageflip.reloadTextures();
|
|
70
|
+
this.pageflip.render();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
this.slotElement.addEventListener('slotchange', this._boundSlotChange);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this._resizeObserver = new ResizeObserver(() => {
|
|
77
|
+
if (this.pageflip) {
|
|
78
|
+
this.pageflip.resize();
|
|
79
|
+
this.pageflip.render();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
this._resizeObserver.observe(this);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
disconnectedCallback() {
|
|
86
|
+
if (this.slotElement && this._boundSlotChange) {
|
|
87
|
+
this.slotElement.removeEventListener('slotchange', this._boundSlotChange);
|
|
88
|
+
}
|
|
89
|
+
if (this._resizeObserver) {
|
|
90
|
+
this._resizeObserver.disconnect();
|
|
91
|
+
this._resizeObserver = null;
|
|
92
|
+
}
|
|
93
|
+
if (this.pageflip) {
|
|
94
|
+
this.pageflip.destroy();
|
|
95
|
+
this.pageflip = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
100
|
+
if (oldValue === newValue) return;
|
|
101
|
+
|
|
102
|
+
if (name === 'engine' && newValue) {
|
|
103
|
+
this.switchEngine(newValue);
|
|
104
|
+
} else if (name === 'page-width' || name === 'page-height' || name === 'page-background') {
|
|
105
|
+
this.applyDimensions();
|
|
106
|
+
if (this.pageflip) this.pageflip.render();
|
|
107
|
+
} else if (name === 'page') {
|
|
108
|
+
if (newValue !== null) {
|
|
109
|
+
const p = parseInt(newValue, 10);
|
|
110
|
+
if (!isNaN(p) && this.pageflip) {
|
|
111
|
+
this.pageflip.gotoPage(p);
|
|
112
|
+
}
|
|
113
|
+
} else if (this.pageflip) {
|
|
114
|
+
this.pageflip.gotoPage(0);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
extractSlides() {
|
|
120
|
+
const pw = this.pageWidth;
|
|
121
|
+
const ph = this.pageHeight;
|
|
122
|
+
const assigned = this.slotElement ? this.slotElement.assignedElements() : [];
|
|
123
|
+
const slideElements = assigned.length > 0
|
|
124
|
+
? assigned.filter((el) => el.matches('hic-pageflip-page') || el.tagName.toLowerCase() === 'hic-pageflip-page')
|
|
125
|
+
: Array.from(this.querySelectorAll('hic-pageflip-page'));
|
|
126
|
+
|
|
127
|
+
this.slides = slideElements.map((el, index) => ({
|
|
128
|
+
pageNum: index + 1,
|
|
129
|
+
element: el,
|
|
130
|
+
pw,
|
|
131
|
+
ph
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
applyDimensions() {
|
|
136
|
+
const pw = this.pageWidth;
|
|
137
|
+
const ph = this.pageHeight;
|
|
138
|
+
const bg = this.pageBackground;
|
|
139
|
+
|
|
140
|
+
this.style.setProperty('--pageflip-width', `${pw}px`);
|
|
141
|
+
this.style.setProperty('--pageflip-height', `${ph}px`);
|
|
142
|
+
this.style.setProperty('--pageflip-background', bg);
|
|
143
|
+
|
|
144
|
+
if (this.pageflip) {
|
|
145
|
+
this.pageflip.setDimensions(pw, ph);
|
|
146
|
+
this.pageflip.resize();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
setupPageflip() {
|
|
151
|
+
if (!this.canvas) return;
|
|
152
|
+
|
|
153
|
+
const mode = this.getAttribute('engine') || this._engineMode || '2d';
|
|
154
|
+
this._engineMode = mode;
|
|
155
|
+
|
|
156
|
+
const pageAttr = this.getAttribute('page');
|
|
157
|
+
const initialPage = pageAttr !== null ? parseInt(pageAttr, 10) : 0;
|
|
158
|
+
|
|
159
|
+
this.pageflip = new Pageflip(this.canvas, this.slides, {
|
|
160
|
+
engine: mode,
|
|
161
|
+
page: !isNaN(initialPage) ? initialPage : 0,
|
|
162
|
+
totalPages: this.slides.length || 6,
|
|
163
|
+
onPageChange: (state) => {
|
|
164
|
+
if (this.getAttribute('page') !== String(state.currentPage)) {
|
|
165
|
+
this.setAttribute('page', `${state.currentPage}`);
|
|
166
|
+
}
|
|
167
|
+
this.dispatchEvent(new CustomEvent('pagechange', {
|
|
168
|
+
bubbles: true,
|
|
169
|
+
composed: true,
|
|
170
|
+
detail: state
|
|
171
|
+
}));
|
|
172
|
+
},
|
|
173
|
+
onFlipProgress: (state) => {
|
|
174
|
+
this.dispatchEvent(new CustomEvent('flipprogress', {
|
|
175
|
+
bubbles: true,
|
|
176
|
+
composed: true,
|
|
177
|
+
detail: state
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
this.applyDimensions();
|
|
183
|
+
this.pageflip.resize();
|
|
184
|
+
this.pageflip.render();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
switchEngine(engineMode) {
|
|
188
|
+
if (this._engineMode === engineMode && this.pageflip) return;
|
|
189
|
+
this._engineMode = engineMode;
|
|
190
|
+
if (this.getAttribute('engine') !== engineMode) {
|
|
191
|
+
this.setAttribute('engine', engineMode);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (this.pageflip) {
|
|
195
|
+
this.pageflip.destroy();
|
|
196
|
+
this.pageflip = null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Recreate fresh canvas inside Shadow Root to allow switching between 2D and WebGL contexts
|
|
200
|
+
if (this.shadowRoot) {
|
|
201
|
+
const oldCanvas = this.canvas;
|
|
202
|
+
const newCanvas = document.createElement('canvas');
|
|
203
|
+
newCanvas.setAttribute('layoutsubtree', '');
|
|
204
|
+
const slot = document.createElement('slot');
|
|
205
|
+
newCanvas.appendChild(slot);
|
|
206
|
+
|
|
207
|
+
if (oldCanvas && oldCanvas.parentNode) {
|
|
208
|
+
oldCanvas.parentNode.replaceChild(newCanvas, oldCanvas);
|
|
209
|
+
} else {
|
|
210
|
+
this.shadowRoot.appendChild(newCanvas);
|
|
211
|
+
}
|
|
212
|
+
this.canvas = newCanvas;
|
|
213
|
+
this.slotElement = slot;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.extractSlides();
|
|
217
|
+
this.setupPageflip();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Public Methods
|
|
221
|
+
flipForward() {
|
|
222
|
+
if (this.pageflip) this.pageflip.flipForward();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
flipBackward() {
|
|
226
|
+
if (this.pageflip) this.pageflip.flipBackward();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
gotoPage(pageNum) {
|
|
230
|
+
if (this.pageflip) this.pageflip.gotoPage(pageNum);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
reloadTextures() {
|
|
234
|
+
if (this.pageflip) this.pageflip.reloadTextures();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
resize() {
|
|
238
|
+
if (this.pageflip) this.pageflip.resize();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
render() {
|
|
242
|
+
if (this.pageflip) this.pageflip.render();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
requestRender() {
|
|
246
|
+
if (this.pageflip) this.pageflip.requestRender();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
getState() {
|
|
250
|
+
return this.pageflip ? this.pageflip.getState() : {
|
|
251
|
+
currentPage: this.currentPage,
|
|
252
|
+
totalPages: this.totalPages,
|
|
253
|
+
currentSpread: this.currentSpread
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Public Properties
|
|
258
|
+
get engine() {
|
|
259
|
+
return this._engineMode;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
set engine(val) {
|
|
263
|
+
this.switchEngine(val);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
get engineMode() {
|
|
267
|
+
return this._engineMode;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
set engineMode(val) {
|
|
271
|
+
this.switchEngine(val);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
get page() {
|
|
275
|
+
return this.currentPage;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
set page(val) {
|
|
279
|
+
this.currentPage = val;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
get currentPage() {
|
|
283
|
+
if (this.pageflip) {
|
|
284
|
+
return this.pageflip.currentPage;
|
|
285
|
+
}
|
|
286
|
+
const pageAttr = this.getAttribute('page');
|
|
287
|
+
return pageAttr !== null ? (parseInt(pageAttr, 10) || 0) : 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
set currentPage(val) {
|
|
291
|
+
const p = parseInt(val, 10);
|
|
292
|
+
if (isNaN(p)) return;
|
|
293
|
+
this.setAttribute('page', `${p}`);
|
|
294
|
+
if (this.pageflip) {
|
|
295
|
+
this.pageflip.gotoPage(p);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
get totalPages() {
|
|
300
|
+
return this.pageflip ? this.pageflip.totalPages : this.slides.length;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
get currentSpread() {
|
|
304
|
+
if (this.pageflip) {
|
|
305
|
+
return this.pageflip.currentSpread;
|
|
306
|
+
}
|
|
307
|
+
const cur = this.currentPage;
|
|
308
|
+
if (cur <= 1) return [0, 1];
|
|
309
|
+
return [cur, cur + 1];
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
get pageWidth() {
|
|
313
|
+
return parseInt(this.getAttribute('page-width') || this.dataset?.pageWidth || this.canvas?.getAttribute('data-pageflip-width'), 10) || 1024;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
set pageWidth(val) {
|
|
317
|
+
this.setAttribute('page-width', val);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
get pageHeight() {
|
|
321
|
+
return parseInt(this.getAttribute('page-height') || this.dataset?.pageHeight || this.canvas?.getAttribute('data-pageflip-height'), 10) || 768;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
set pageHeight(val) {
|
|
325
|
+
this.setAttribute('page-height', val);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
get pageBackground() {
|
|
329
|
+
return this.getAttribute('page-background') || this.dataset?.pageBackground || this.canvas?.getAttribute('data-pageflip-background') || 'white';
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
set pageBackground(val) {
|
|
333
|
+
this.setAttribute('page-background', val);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (typeof customElements !== 'undefined' && !customElements.get('hic-pageflip')) {
|
|
338
|
+
customElements.define('hic-pageflip', HICPageflip);
|
|
339
|
+
}
|