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,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 2D PageFlip Geometry & Vector Math
|
|
3
|
+
* Based on the classic Flash PageFlip engine & Xerox PARC page curl research.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const Easing = {
|
|
7
|
+
easeOutCubic: (t) => 1 - Math.pow(1 - t, 3),
|
|
8
|
+
easeInOutCubic: (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
|
|
9
|
+
easeOutQuad: (t) => 1 - (1 - t) * (1 - t),
|
|
10
|
+
easeInOutQuad: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
|
|
11
|
+
easeOutBack: (t) => {
|
|
12
|
+
const c1 = 1.70158;
|
|
13
|
+
const c3 = c1 + 1;
|
|
14
|
+
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Constrains dragged point P to respect paper inextensibility (rigidity) relative to spine anchors.
|
|
20
|
+
* @param {number} px - Dragged point X
|
|
21
|
+
* @param {number} py - Dragged point Y
|
|
22
|
+
* @param {number} sx - Start corner X (W or -W)
|
|
23
|
+
* @param {number} sy - Start corner Y (-H/2 to H/2)
|
|
24
|
+
* @param {number} pw - Page width
|
|
25
|
+
* @param {number} ph - Page height
|
|
26
|
+
* @returns {{x: number, y: number}} Constrained (px, py)
|
|
27
|
+
*/
|
|
28
|
+
export function constrainPaper(px, py, sx, sy, pw, ph) {
|
|
29
|
+
const topAnchorY = -ph / 2;
|
|
30
|
+
const botAnchorY = ph / 2;
|
|
31
|
+
|
|
32
|
+
// Maximum allowed distance from dragged corner to spine corners
|
|
33
|
+
const maxRadiusTop = Math.hypot(pw, sy - topAnchorY);
|
|
34
|
+
const maxRadiusBot = Math.hypot(pw, botAnchorY - sy);
|
|
35
|
+
|
|
36
|
+
let x = px;
|
|
37
|
+
let y = py;
|
|
38
|
+
|
|
39
|
+
// Constraint 1: Top spine anchor (0, topAnchorY)
|
|
40
|
+
const dTop = Math.hypot(x, y - topAnchorY);
|
|
41
|
+
if (dTop > maxRadiusTop) {
|
|
42
|
+
const scale = maxRadiusTop / dTop;
|
|
43
|
+
x = x * scale;
|
|
44
|
+
y = topAnchorY + (y - topAnchorY) * scale;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Constraint 2: Bottom spine anchor (0, botAnchorY)
|
|
48
|
+
const dBot = Math.hypot(x, y - botAnchorY);
|
|
49
|
+
if (dBot > maxRadiusBot) {
|
|
50
|
+
const scale = maxRadiusBot / dBot;
|
|
51
|
+
x = x * scale;
|
|
52
|
+
y = botAnchorY + (y - botAnchorY) * scale;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Bound within horizontal travel limits
|
|
56
|
+
if (sx > 0) {
|
|
57
|
+
x = Math.max(-pw, Math.min(pw, x));
|
|
58
|
+
} else {
|
|
59
|
+
x = Math.min(pw, Math.max(-pw, x));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { x, y };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Calculates fold crease line parameters, normal, intersections, and signed distance.
|
|
67
|
+
* @param {number} px - Dragged corner X
|
|
68
|
+
* @param {number} py - Dragged corner Y
|
|
69
|
+
* @param {number} sx - Start corner X
|
|
70
|
+
* @param {number} sy - Start corner Y
|
|
71
|
+
* @param {number} pw - Page width
|
|
72
|
+
* @param {number} ph - Page height
|
|
73
|
+
* @returns {object|null} Fold line geometry
|
|
74
|
+
*/
|
|
75
|
+
export function calculateFold(px, py, sx, sy, pw, ph) {
|
|
76
|
+
const vx = px - sx;
|
|
77
|
+
const vy = py - sy;
|
|
78
|
+
const len = Math.hypot(vx, vy);
|
|
79
|
+
|
|
80
|
+
if (len < 0.5) {
|
|
81
|
+
return null; // Fold is negligible / page flat
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const nx = vx / len;
|
|
85
|
+
const ny = vy / len;
|
|
86
|
+
const cx = (sx + px) / 2;
|
|
87
|
+
const cy = (sy + py) / 2;
|
|
88
|
+
const foldAngle = Math.atan2(ny, nx) + Math.PI / 2;
|
|
89
|
+
|
|
90
|
+
// Page rectangle X range: [0, pw] for right page, [-pw, 0] for left page
|
|
91
|
+
const minX = sx > 0 ? 0 : -pw;
|
|
92
|
+
const maxX = sx > 0 ? pw : 0;
|
|
93
|
+
const minY = -ph / 2;
|
|
94
|
+
const maxY = ph / 2;
|
|
95
|
+
|
|
96
|
+
// Find intersections of line (X - cx)*nx + (Y - cy)*ny = 0 with page box edges
|
|
97
|
+
const intersections = [];
|
|
98
|
+
const eps = 1e-6;
|
|
99
|
+
|
|
100
|
+
// 1. Top edge (Y = minY)
|
|
101
|
+
if (Math.abs(nx) > eps) {
|
|
102
|
+
const ix = cx - ((minY - cy) * ny) / nx;
|
|
103
|
+
if (ix >= minX - eps && ix <= maxX + eps) {
|
|
104
|
+
intersections.push({ x: Math.max(minX, Math.min(maxX, ix)), y: minY, edge: 'top' });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 2. Bottom edge (Y = maxY)
|
|
109
|
+
if (Math.abs(nx) > eps) {
|
|
110
|
+
const ix = cx - ((maxY - cy) * ny) / nx;
|
|
111
|
+
if (ix >= minX - eps && ix <= maxX + eps) {
|
|
112
|
+
intersections.push({ x: Math.max(minX, Math.min(maxX, ix)), y: maxY, edge: 'bottom' });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 3. Outer edge (X = sx)
|
|
117
|
+
if (Math.abs(ny) > eps) {
|
|
118
|
+
const iy = cy - ((sx - cx) * nx) / ny;
|
|
119
|
+
if (iy >= minY - eps && iy <= maxY + eps) {
|
|
120
|
+
intersections.push({ x: sx, y: Math.max(minY, Math.min(maxY, iy)), edge: 'outer' });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 4. Spine edge (X = 0)
|
|
125
|
+
if (Math.abs(ny) > eps) {
|
|
126
|
+
const iy = cy - ((0 - cx) * nx) / ny;
|
|
127
|
+
if (iy >= minY - eps && iy <= maxY + eps) {
|
|
128
|
+
intersections.push({ x: 0, y: Math.max(minY, Math.min(maxY, iy)), edge: 'spine' });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Remove duplicate intersection points that might happen at corners
|
|
133
|
+
const uniqueIntersections = [];
|
|
134
|
+
for (const pt of intersections) {
|
|
135
|
+
const isDup = uniqueIntersections.some(
|
|
136
|
+
(u) => Math.hypot(u.x - pt.x, u.y - pt.y) < 0.1
|
|
137
|
+
);
|
|
138
|
+
if (!isDup) uniqueIntersections.push(pt);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
px,
|
|
143
|
+
py,
|
|
144
|
+
sx,
|
|
145
|
+
sy,
|
|
146
|
+
vx,
|
|
147
|
+
vy,
|
|
148
|
+
len,
|
|
149
|
+
nx,
|
|
150
|
+
ny,
|
|
151
|
+
cx,
|
|
152
|
+
cy,
|
|
153
|
+
foldAngle,
|
|
154
|
+
intersections: uniqueIntersections,
|
|
155
|
+
minX,
|
|
156
|
+
maxX,
|
|
157
|
+
minY,
|
|
158
|
+
maxY
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Reflects a 2D point across the fold line.
|
|
164
|
+
*/
|
|
165
|
+
export function reflectPoint(x, y, fold) {
|
|
166
|
+
const dist = (x - fold.cx) * fold.nx + (y - fold.cy) * fold.ny;
|
|
167
|
+
return {
|
|
168
|
+
x: x - 2 * dist * fold.nx,
|
|
169
|
+
y: y - 2 * dist * fold.ny
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Returns the signed distance from a point to the fold line.
|
|
175
|
+
* Positive on start corner side S, negative on dragged corner side P.
|
|
176
|
+
*/
|
|
177
|
+
export function foldSignedDistance(x, y, fold) {
|
|
178
|
+
return (x - fold.cx) * fold.nx + (y - fold.cy) * fold.ny;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Clamps value between min and max.
|
|
183
|
+
*/
|
|
184
|
+
export function clamp(v, min, max) {
|
|
185
|
+
return Math.max(min, Math.min(max, v));
|
|
186
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pageflip entry point.
|
|
3
|
+
* Exports custom elements (<hic-pageflip>, <hic-pageflip-page>) and core Pageflip engine.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { HICPageflip } from './components/hic-pageflip.js';
|
|
7
|
+
export { HICPageflipPage } from './components/hic-pageflip-page.js';
|
|
8
|
+
export { Pageflip } from './core/pageflip.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hic-pageflip",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interactive page flip book viewer built with HTML-in-Canvas",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"module": "./index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./components/*": "./components/*",
|
|
11
|
+
"./core/*": "./core/*"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pageflip",
|
|
15
|
+
"canvas",
|
|
16
|
+
"flipbook",
|
|
17
|
+
"html-in-canvas"
|
|
18
|
+
],
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "Bramus Van Damme",
|
|
21
|
+
"email": "bramus@bram.us",
|
|
22
|
+
"url": "https://www.bram.us/"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/bramus/hic-pageflip.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/bramus/hic-pageflip/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://hic-pageflip.netlify.app"
|
|
33
|
+
}
|