@sliiu/present-transition-fx 0.0.0-stage → 0.1.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.
Files changed (48) hide show
  1. package/COPYRIGHT +5 -0
  2. package/LICENSE +661 -0
  3. package/THIRD-PARTY-NOTICES.md +221 -0
  4. package/dist/cjs/2d.js +487 -0
  5. package/dist/cjs/3d.js +566 -0
  6. package/dist/cjs/geometry.js +141 -0
  7. package/dist/cjs/index.js +98 -0
  8. package/dist/cjs/morph-audit.js +603 -0
  9. package/dist/cjs/morph-match.js +103 -0
  10. package/dist/cjs/morph-path.js +649 -0
  11. package/dist/cjs/morph.js +223 -0
  12. package/dist/cjs/package.json +1 -0
  13. package/dist/cjs/registry.js +130 -0
  14. package/dist/cjs/snapshot.js +420 -0
  15. package/dist/cjs/twin.js +88 -0
  16. package/dist/cjs/types.js +2 -0
  17. package/dist/cjs/util.js +50 -0
  18. package/dist/cjs/webgl.js +271 -0
  19. package/dist/esm/2d.js +454 -0
  20. package/dist/esm/3d.js +538 -0
  21. package/dist/esm/geometry.js +127 -0
  22. package/dist/esm/index.js +6 -0
  23. package/dist/esm/morph-audit.js +558 -0
  24. package/dist/esm/morph-match.js +97 -0
  25. package/dist/esm/morph-path.js +636 -0
  26. package/dist/esm/morph.js +219 -0
  27. package/dist/esm/registry.js +91 -0
  28. package/dist/esm/snapshot.js +414 -0
  29. package/dist/esm/twin.js +84 -0
  30. package/dist/esm/types.js +1 -0
  31. package/dist/esm/util.js +43 -0
  32. package/dist/esm/webgl.js +231 -0
  33. package/dist/types/2d.d.ts +40 -0
  34. package/dist/types/3d.d.ts +38 -0
  35. package/dist/types/geometry.d.ts +38 -0
  36. package/dist/types/index.d.ts +7 -0
  37. package/dist/types/morph-audit.d.ts +127 -0
  38. package/dist/types/morph-match.d.ts +28 -0
  39. package/dist/types/morph-path.d.ts +37 -0
  40. package/dist/types/morph.d.ts +2 -0
  41. package/dist/types/registry.d.ts +6 -0
  42. package/dist/types/snapshot.d.ts +19 -0
  43. package/dist/types/twin.d.ts +2 -0
  44. package/dist/types/types.d.ts +13 -0
  45. package/dist/types/util.d.ts +12 -0
  46. package/dist/types/webgl.d.ts +5 -0
  47. package/package.json +26 -4
  48. package/README.md +0 -3
package/dist/cjs/3d.js ADDED
@@ -0,0 +1,566 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.vortex = exports.warp = exports.ripple = exports.curtains = exports.drape = exports.switchFx = exports.flip = exports.peelOff = exports.ferris = exports.rotate = exports.flythrough = exports.origami = exports.airplane = exports.crush = exports.wind = exports.pageCurlSingle = exports.pageCurl = exports.window = exports.doors = exports.conveyor = exports.orbit = exports.gallery = exports.box = exports.prism = exports.cube = void 0;
4
+ const geometry_1 = require("./geometry");
5
+ const twin_1 = require("./twin");
6
+ const util_1 = require("./util");
7
+ function wrap3d(ctx) {
8
+ const parent = ctx.stage.parentElement;
9
+ if (!parent)
10
+ return { wrap: ctx.stage, cleanup: () => undefined };
11
+ const prevPerspective = parent.style.perspective;
12
+ const prevTransformStyle = parent.style.transformStyle;
13
+ parent.style.perspective = `${Math.max(ctx.stageSize.w, ctx.stageSize.h) * 1.5}px`;
14
+ parent.style.transformStyle = "preserve-3d";
15
+ ctx.stage.style.transformStyle = "preserve-3d";
16
+ ctx.ghost.style.transformStyle = "preserve-3d";
17
+ ctx.stage.style.backfaceVisibility = "hidden";
18
+ ctx.ghost.style.backfaceVisibility = "hidden";
19
+ return {
20
+ wrap: parent,
21
+ cleanup: () => {
22
+ parent.style.perspective = prevPerspective;
23
+ parent.style.transformStyle = prevTransformStyle;
24
+ ctx.stage.style.transformStyle = "";
25
+ ctx.ghost.style.transformStyle = "";
26
+ ctx.stage.style.backfaceVisibility = "";
27
+ ctx.ghost.style.backfaceVisibility = "";
28
+ },
29
+ };
30
+ }
31
+ const cube = async (ctx) => {
32
+ const dir = String(ctx.params.dir ?? "left");
33
+ const { cleanup } = wrap3d(ctx);
34
+ const w = ctx.stageSize.w;
35
+ const h = ctx.stageSize.h;
36
+ const axis = dir === "left" || dir === "right" ? "Y" : "X";
37
+ const sign = dir === "left" || dir === "up" ? -1 : 1;
38
+ const half = (axis === "Y" ? w : h) / 2;
39
+ const sBase = ctx.stage.style.transform || "";
40
+ const gBase = ctx.ghost.style.transform || "";
41
+ ctx.stage.style.transformOrigin = "center center";
42
+ ctx.ghost.style.transformOrigin = "center center";
43
+ const a = ctx.stage.animate([
44
+ { transform: `translateZ(-${half}px) rotate${axis}(${sign * -90}deg) translateZ(${half}px) ${sBase}` },
45
+ { transform: `translateZ(-${half}px) rotate${axis}(0deg) translateZ(${half}px) ${sBase}` },
46
+ ], { duration: (0, util_1.ms)(ctx), easing: (0, util_1.easing)(ctx), fill: "forwards" });
47
+ const b = ctx.ghost.animate([
48
+ { transform: `translateZ(-${half}px) rotate${axis}(0deg) translateZ(${half}px) ${gBase}` },
49
+ { transform: `translateZ(-${half}px) rotate${axis}(${sign * 90}deg) translateZ(${half}px) ${gBase}` },
50
+ ], { duration: (0, util_1.ms)(ctx), easing: (0, util_1.easing)(ctx), fill: "forwards" });
51
+ await (0, util_1.runAll)([a, b], cleanup);
52
+ };
53
+ exports.cube = cube;
54
+ /** 三棱柱转 120°,和立方体的 90° 分开。左右绕 Y,上下绕 X。 */
55
+ const prism = async (ctx) => {
56
+ const dir = String(ctx.params.dir ?? "left");
57
+ const { cleanup } = wrap3d(ctx);
58
+ const alongY = dir === "left" || dir === "right";
59
+ const half = (alongY ? ctx.stageSize.w : ctx.stageSize.h) / 2;
60
+ const axis = alongY ? "Y" : "X";
61
+ const sign = dir === "left" || dir === "up" ? -1 : 1;
62
+ const sBase = ctx.stage.style.transform || "";
63
+ const gBase = ctx.ghost.style.transform || "";
64
+ ctx.stage.style.transformOrigin = "center center";
65
+ ctx.ghost.style.transformOrigin = "center center";
66
+ const a = ctx.stage.animate([
67
+ { transform: `translateZ(-${half}px) rotate${axis}(${sign * -120}deg) translateZ(${half}px) ${sBase}` },
68
+ { transform: `translateZ(-${half}px) rotate${axis}(0deg) translateZ(${half}px) ${sBase}` },
69
+ ], { duration: (0, util_1.ms)(ctx), easing: (0, util_1.easing)(ctx), fill: "forwards" });
70
+ const b = ctx.ghost.animate([
71
+ { transform: `translateZ(-${half}px) rotate${axis}(0deg) translateZ(${half}px) ${gBase}` },
72
+ { transform: `translateZ(-${half}px) rotate${axis}(${sign * 120}deg) translateZ(${half}px) ${gBase}` },
73
+ ], { duration: (0, util_1.ms)(ctx), easing: (0, util_1.easing)(ctx), fill: "forwards" });
74
+ await (0, util_1.runAll)([a, b], cleanup);
75
+ };
76
+ exports.prism = prism;
77
+ /** 走进盒子:新页从内侧转出来,旧页往外让开。和立方体外表面对转分开。 */
78
+ const box = async (ctx) => {
79
+ const dir = String(ctx.params.dir ?? "left");
80
+ const { cleanup } = wrap3d(ctx);
81
+ const alongY = dir === "left" || dir === "right";
82
+ const axis = alongY ? "Y" : "X";
83
+ const sign = dir === "left" || dir === "up" ? -1 : 1;
84
+ const half = (alongY ? ctx.stageSize.w : ctx.stageSize.h) / 2;
85
+ const sBase = ctx.stage.style.transform || "";
86
+ const gBase = ctx.ghost.style.transform || "";
87
+ ctx.stage.style.transformOrigin = "center center";
88
+ ctx.ghost.style.transformOrigin = "center center";
89
+ const a = ctx.stage.animate([
90
+ { transform: `translateZ(${half * 0.35}px) rotate${axis}(${sign * 70}deg) scale(0.82) ${sBase}`, opacity: 0.2 },
91
+ { transform: `translateZ(0px) rotate${axis}(0deg) scale(1) ${sBase}`, opacity: 1 },
92
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
93
+ const b = ctx.ghost.animate([
94
+ { transform: gBase, opacity: 1 },
95
+ { transform: `translateZ(-${half}px) rotate${axis}(${sign * -55}deg) scale(1.15) ${gBase}`, opacity: 0 },
96
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
97
+ await (0, util_1.runAll)([a, b], cleanup);
98
+ };
99
+ exports.box = box;
100
+ const gallery = async (ctx) => {
101
+ const dir = String(ctx.params.dir ?? "right");
102
+ const { cleanup } = wrap3d(ctx);
103
+ const sign = dir === "left" ? -1 : 1;
104
+ const sBase = ctx.stage.style.transform || "";
105
+ const gBase = ctx.ghost.style.transform || "";
106
+ const a = ctx.stage.animate([
107
+ { transform: `translateX(${sign * ctx.stageSize.w}px) rotateY(${-sign * 90}deg) ${sBase}` },
108
+ { transform: `translateX(0px) rotateY(0deg) ${sBase}` },
109
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
110
+ const b = ctx.ghost.animate([
111
+ { transform: `translateX(0px) rotateY(0deg) ${gBase}` },
112
+ { transform: `translateX(${-sign * ctx.stageSize.w}px) rotateY(${sign * 90}deg) ${gBase}` },
113
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
114
+ await (0, util_1.runAll)([a, b], cleanup);
115
+ };
116
+ exports.gallery = gallery;
117
+ const orbit = async (ctx) => {
118
+ const dir = String(ctx.params.dir ?? "left");
119
+ const { cleanup } = wrap3d(ctx);
120
+ const sign = dir === "left" ? -1 : 1;
121
+ const sBase = ctx.stage.style.transform || "";
122
+ const gBase = ctx.ghost.style.transform || "";
123
+ const a = ctx.stage.animate([
124
+ { transform: `translateZ(-200px) rotateY(${sign * 90}deg) translateZ(200px) ${sBase}`, opacity: 0 },
125
+ { transform: `translateZ(0) rotateY(0) translateZ(0) ${sBase}`, opacity: 1 },
126
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
127
+ const b = ctx.ghost.animate([
128
+ { transform: gBase, opacity: 1 },
129
+ { transform: `translateZ(-200px) rotateY(${-sign * 90}deg) ${gBase}`, opacity: 0 },
130
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
131
+ await (0, util_1.runAll)([a, b], cleanup);
132
+ };
133
+ exports.orbit = orbit;
134
+ const conveyor = async (ctx) => {
135
+ const dir = String(ctx.params.dir ?? "right");
136
+ const { cleanup } = wrap3d(ctx);
137
+ const sign = dir === "left" ? -1 : 1;
138
+ const sBase = ctx.stage.style.transform || "";
139
+ const gBase = ctx.ghost.style.transform || "";
140
+ const a = ctx.stage.animate([
141
+ { transform: `translateX(${sign * ctx.stageSize.w}px) translateZ(-400px) ${sBase}` },
142
+ { transform: `translateX(0) translateZ(0) ${sBase}` },
143
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
144
+ const b = ctx.ghost.animate([
145
+ { transform: gBase },
146
+ { transform: `translateX(${-sign * ctx.stageSize.w}px) translateZ(-400px) ${gBase}` },
147
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
148
+ await (0, util_1.runAll)([a, b], cleanup);
149
+ };
150
+ exports.conveyor = conveyor;
151
+ function pageHalves(ctx, vert) {
152
+ const left = (0, twin_1.mountTwin)(ctx.ghost, vert ? "inset(0 50% 0 0)" : "inset(0 0 50% 0)");
153
+ const right = (0, twin_1.mountTwin)(ctx.ghost, vert ? "inset(0 0 0 50%)" : "inset(50% 0 0 0)");
154
+ const prev = ctx.ghost.style.visibility;
155
+ ctx.ghost.style.visibility = "hidden";
156
+ return { left, right, prev };
157
+ }
158
+ /** 旧页对开。垂直沿左右铰链转开,水平沿上下铰链转开。 */
159
+ const doors = async (ctx) => {
160
+ const vert = String(ctx.params.orient ?? "vert") !== "horz";
161
+ const { cleanup } = wrap3d(ctx);
162
+ const { left, right, prev } = pageHalves(ctx, vert);
163
+ left.style.transformOrigin = vert ? "left center" : "center top";
164
+ right.style.transformOrigin = vert ? "right center" : "center bottom";
165
+ const openL = vert ? "perspective(900px) rotateY(-105deg)" : "perspective(900px) rotateX(105deg)";
166
+ const openR = vert ? "perspective(900px) rotateY(105deg)" : "perspective(900px) rotateX(-105deg)";
167
+ const dur = (0, util_1.ms)(ctx);
168
+ await (0, util_1.runAll)([
169
+ left.animate([{ transform: "none", opacity: 1 }, { transform: openL, opacity: 0 }], { duration: dur, easing: util_1.STD_EASE, fill: "forwards" }),
170
+ right.animate([{ transform: "none", opacity: 1 }, { transform: openR, opacity: 0 }], { duration: dur, easing: util_1.STD_EASE, fill: "forwards" }),
171
+ ], () => {
172
+ (0, twin_1.dropTwins)([left, right], ctx.ghost, prev);
173
+ cleanup();
174
+ });
175
+ };
176
+ exports.doors = doors;
177
+ /** 窗:两扇平着滑开,不绕铰链转。和门分开。 */
178
+ const window = async (ctx) => {
179
+ const vert = String(ctx.params.orient ?? "vert") !== "horz";
180
+ const { left, right, prev } = pageHalves(ctx, vert);
181
+ const offL = vert ? "translateX(-100%)" : "translateY(-100%)";
182
+ const offR = vert ? "translateX(100%)" : "translateY(100%)";
183
+ const baseL = left.style.transform || "";
184
+ const baseR = right.style.transform || "";
185
+ const dur = (0, util_1.ms)(ctx);
186
+ await (0, util_1.runAll)([
187
+ left.animate([{ transform: baseL }, { transform: `${offL} ${baseL}` }], { duration: dur, easing: util_1.STD_EASE, fill: "forwards" }),
188
+ right.animate([{ transform: baseR }, { transform: `${offR} ${baseR}` }], { duration: dur, easing: util_1.STD_EASE, fill: "forwards" }),
189
+ ], () => (0, twin_1.dropTwins)([left, right], ctx.ghost, prev));
190
+ };
191
+ exports.window = window;
192
+ // 旧页绕一侧边缘翻起:towardRight 时铰链在右缘、左缘翻向右侧,否则镜像。
193
+ async function curlAway(ctx, towardRight) {
194
+ const { cleanup } = wrap3d(ctx);
195
+ const gBase = ctx.ghost.style.transform || "";
196
+ ctx.ghost.style.transformOrigin = towardRight ? "right center" : "left center";
197
+ const deg = towardRight ? -160 : 160;
198
+ const a = ctx.ghost.animate([
199
+ { transform: `perspective(1200px) rotateY(0deg) ${gBase}` },
200
+ { transform: `perspective(1200px) rotateY(${deg}deg) ${gBase}`, opacity: 0 },
201
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
202
+ await (0, util_1.runAll)([a], cleanup);
203
+ }
204
+ const pageCurl = (ctx) => curlAway(ctx, true);
205
+ exports.pageCurl = pageCurl;
206
+ /** p15 pageCurlSingle:只掀起一角。整页卷曲走 pageCurl 的书页翻边。 */
207
+ const pageCurlSingle = async (ctx) => {
208
+ const dir = String(ctx.params.dir ?? "left");
209
+ const fromRight = dir === "right";
210
+ const fromTop = dir === "up" || dir === "down";
211
+ const { cleanup } = wrap3d(ctx);
212
+ ctx.ghost.style.transformOrigin = `${fromRight ? "0%" : "100%"} ${fromTop ? "0%" : "100%"}`;
213
+ const gBase = ctx.ghost.style.transform || "";
214
+ const ry = fromRight ? -62 : 62;
215
+ const rx = fromTop ? -36 : 36;
216
+ const a = ctx.ghost.animate([
217
+ { transform: gBase, opacity: 1 },
218
+ { transform: `rotateX(${rx * 0.45}deg) rotateY(${ry * 0.45}deg) ${gBase}`, opacity: 1, offset: 0.4 },
219
+ { transform: `rotateX(${rx}deg) rotateY(${ry}deg) translateZ(80px) ${gBase}`, opacity: 0 },
220
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
221
+ await (0, util_1.runAll)([a], () => {
222
+ ctx.ghost.style.transformOrigin = "";
223
+ cleanup();
224
+ });
225
+ };
226
+ exports.pageCurlSingle = pageCurlSingle;
227
+ /** p15 wind:旧页切成细条,顺着风向依次吹出。向右 / 向下镜像。 */
228
+ const wind = async (ctx) => {
229
+ const dir = String(ctx.params.dir ?? "left");
230
+ const vertical = dir === "up" || dir === "down";
231
+ const sign = dir === "right" || dir === "down" ? 1 : -1;
232
+ const count = 12;
233
+ const strips = [];
234
+ for (let i = 0; i < count; i++) {
235
+ const start = (i / count) * 100;
236
+ const end = ((count - i - 1) / count) * 100;
237
+ const clip = vertical ? `inset(0 ${end}% 0 ${start}%)` : `inset(${start}% 0 ${end}% 0)`;
238
+ strips.push((0, twin_1.mountTwin)(ctx.ghost, clip));
239
+ }
240
+ const prev = ctx.ghost.style.visibility;
241
+ ctx.ghost.style.visibility = "hidden";
242
+ const dur = (0, util_1.ms)(ctx);
243
+ const anims = strips.map((el, i) => {
244
+ const base = el.style.transform || "";
245
+ const shift = vertical
246
+ ? `translate(0, ${sign * 120}%) rotate(${sign * 16}deg)`
247
+ : `translate(${sign * 120}%, ${sign * -6}%) rotate(${sign * 18}deg)`;
248
+ return el.animate([
249
+ { transform: base, opacity: 1 },
250
+ { transform: `${shift} ${base}`, opacity: 0 },
251
+ ], { duration: dur * 0.62, delay: (i / count) * dur * 0.4, easing: (0, util_1.easing)(ctx), fill: "forwards" });
252
+ });
253
+ await (0, util_1.runAll)(anims, () => (0, twin_1.dropTwins)(strips, ctx.ghost, prev));
254
+ };
255
+ exports.wind = wind;
256
+ /** p15 crush:旧页朝选定的边压成一条线。 */
257
+ const crush = async (ctx) => {
258
+ const dir = String(ctx.params.dir ?? "left");
259
+ const vertical = dir === "up" || dir === "down";
260
+ ctx.ghost.style.transformOrigin = vertical
261
+ ? (dir === "down" ? "center bottom" : "center top")
262
+ : (dir === "right" ? "right center" : "left center");
263
+ const gBase = ctx.ghost.style.transform || "";
264
+ const flat = vertical ? "scaleX(1.12) scaleY(0.02)" : "scaleX(0.02) scaleY(1.12)";
265
+ const a = ctx.ghost.animate([
266
+ { transform: gBase, opacity: 1 },
267
+ { transform: `${flat} ${gBase}`, opacity: 0 },
268
+ ], { duration: (0, util_1.ms)(ctx), easing: (0, util_1.easing)(ctx), fill: "forwards" });
269
+ await (0, util_1.runAll)([a], () => {
270
+ ctx.ghost.style.transformOrigin = "";
271
+ });
272
+ };
273
+ exports.crush = crush;
274
+ // [MS-PPTX] p15:prstTrans prst="airplane":旧页原地折成纸飞机(两个前角折向中线成机头,
275
+ // 右缘收成机尾),再朝左上飞出画面,新页一直静止在下面。invX(params.dir="right")镜像成朝右上。
276
+ // clip-path 各帧点数必须相同,WAAPI 才能插值:左边两个角都收到机头,右缘三个点收成尾翼缺口。
277
+ const AIRPLANE_SHEET = [[0, 0], [100, 0], [100, 40], [100, 50], [100, 60], [100, 100], [0, 100]];
278
+ const AIRPLANE_PLANE = [[4, 49], [96, 8], [84, 42], [92, 50], [84, 58], [96, 92], [4, 51]];
279
+ function airplanePolygon(pts, mirror) {
280
+ return `polygon(${pts.map(([x, y]) => `${mirror ? 100 - x : x}% ${y}%`).join(", ")})`;
281
+ }
282
+ const airplane = async (ctx) => {
283
+ const ghost = ctx.ghost;
284
+ const doc = ghost.ownerDocument;
285
+ const mirror = String(ctx.params.dir ?? "left") === "right";
286
+ const sx = mirror ? -1 : 1;
287
+ const gBase = ghost.style.transform || "";
288
+ const tf = (x, y, deg, s) => `translate(${-sx * x}%, ${y}%) rotate(${sx * deg}deg) scale(${s}) ${gBase}`.trim();
289
+ const sheet = airplanePolygon(AIRPLANE_SHEET, mirror);
290
+ const plane = airplanePolygon(AIRPLANE_PLANE, mirror);
291
+ // clip-path 会连 filter 一起裁掉,投影只能挂在外层;外层 0×0 + overflow 可见,不改变 ghost 的定位基准。
292
+ const parent = ghost.parentElement;
293
+ const shadowWrap = doc.createElement("div");
294
+ shadowWrap.style.cssText = "position:absolute;left:0;top:0;width:0;height:0;overflow:visible;pointer-events:none";
295
+ shadowWrap.style.zIndex = ghost.style.zIndex || "2";
296
+ if (parent) {
297
+ parent.insertBefore(shadowWrap, ghost);
298
+ shadowWrap.appendChild(ghost);
299
+ }
300
+ // 折痕:机身中线一道暗线,上翼略亮、下翼略暗;跟随 ghost 一起被裁成飞机形。
301
+ const crease = doc.createElement("div");
302
+ crease.style.cssText = [
303
+ "position:absolute",
304
+ "inset:0",
305
+ "pointer-events:none",
306
+ "opacity:0",
307
+ "background:linear-gradient(to bottom, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0) 44%, rgba(0,0,0,0.5) 50%, rgba(0,0,0,0) 56%, rgba(0,0,0,0.28) 100%)",
308
+ ].join(";");
309
+ ghost.appendChild(crease);
310
+ const prevOrigin = ghost.style.transformOrigin;
311
+ const prevClip = ghost.style.clipPath;
312
+ ghost.style.transformOrigin = "50% 50%";
313
+ const dur = (0, util_1.ms)(ctx);
314
+ const fold = "cubic-bezier(0.45, 0, 0.55, 1)";
315
+ const a = ghost.animate([
316
+ { offset: 0, clipPath: sheet, transform: tf(0, 0, 0, 1), easing: fold },
317
+ { offset: 0.1, clipPath: sheet, transform: tf(0, 0, 0, 0.94), easing: fold },
318
+ { offset: 0.45, clipPath: plane, transform: tf(0, 0, 0, 0.5), easing: fold },
319
+ // 起飞前先往反方向压一下,再加速爬升。
320
+ { offset: 0.55, clipPath: plane, transform: tf(-6, 4, 4, 0.5), easing: "cubic-bezier(0.55, 0, 0.9, 0.45)" },
321
+ { offset: 1, clipPath: plane, transform: tf(135, -110, 24, 0.34) },
322
+ ], { duration: dur, fill: "forwards" });
323
+ const b = crease.animate([
324
+ { offset: 0, opacity: 0 },
325
+ { offset: 0.1, opacity: 0 },
326
+ { offset: 0.45, opacity: 1 },
327
+ { offset: 1, opacity: 1 },
328
+ ], { duration: dur, fill: "forwards" });
329
+ const c = shadowWrap.animate([
330
+ { offset: 0, filter: "drop-shadow(0 0 0 rgba(0,0,0,0))" },
331
+ { offset: 0.45, filter: "drop-shadow(0 12px 16px rgba(0,0,0,0.45))" },
332
+ { offset: 1, filter: "drop-shadow(0 24px 22px rgba(0,0,0,0.35))" },
333
+ ], { duration: dur, fill: "forwards" });
334
+ await (0, util_1.runAll)([a, b, c], () => {
335
+ crease.remove();
336
+ ghost.style.transformOrigin = prevOrigin;
337
+ ghost.style.clipPath = prevClip;
338
+ if (parent) {
339
+ parent.insertBefore(ghost, shadowWrap);
340
+ shadowWrap.remove();
341
+ }
342
+ });
343
+ };
344
+ exports.airplane = airplane;
345
+ /** 对折两次再飞走。向右时折痕在右缘。和翻页的单次卷起分开。 */
346
+ const origami = async (ctx) => {
347
+ const mirror = String(ctx.params.dir ?? "left") === "right";
348
+ const gBase = ctx.ghost.style.transform || "";
349
+ const origin = ctx.ghost.style.transformOrigin;
350
+ ctx.ghost.style.transformOrigin = mirror ? "right center" : "left center";
351
+ const fly = mirror ? -130 : 130;
352
+ const a = ctx.ghost.animate([
353
+ { offset: 0, transform: `scaleX(1) ${gBase}`, opacity: 1 },
354
+ { offset: 0.42, transform: `scaleX(0.06) ${gBase}`, opacity: 1 },
355
+ { offset: 1, transform: `scaleX(0.06) translate(${fly}%, -36%) rotate(${mirror ? -18 : 18}deg) ${gBase}`, opacity: 0 },
356
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
357
+ await (0, util_1.runAll)([a], () => {
358
+ ctx.ghost.style.transformOrigin = origin;
359
+ });
360
+ };
361
+ exports.origami = origami;
362
+ const flythrough = async (ctx) => {
363
+ const out = String(ctx.params.dir ?? "in") === "out";
364
+ const { cleanup } = wrap3d(ctx);
365
+ const sBase = ctx.stage.style.transform || "";
366
+ const gBase = ctx.ghost.style.transform || "";
367
+ const anims = out
368
+ ? [
369
+ ctx.stage.animate([
370
+ { transform: `${sBase} translateZ(-1400px) scale(0.35)`, opacity: 0 },
371
+ { transform: sBase, opacity: 1 },
372
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" }),
373
+ ctx.ghost.animate([{ opacity: 1 }, { opacity: 0 }], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" }),
374
+ ]
375
+ : [
376
+ ctx.ghost.animate([
377
+ { transform: gBase, opacity: 1 },
378
+ { transform: `${gBase} translateZ(900px) scale(2.6)`, opacity: 0 },
379
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" }),
380
+ ];
381
+ await (0, util_1.runAll)(anims, cleanup);
382
+ };
383
+ exports.flythrough = flythrough;
384
+ const rotate = async (ctx) => {
385
+ const dir = String(ctx.params.dir ?? "left");
386
+ const { cleanup } = wrap3d(ctx);
387
+ const axis = dir === "left" || dir === "right" ? "Y" : "X";
388
+ const sign = dir === "left" || dir === "up" ? -1 : 1;
389
+ const sBase = ctx.stage.style.transform || "";
390
+ const gBase = ctx.ghost.style.transform || "";
391
+ const a = ctx.stage.animate([
392
+ { transform: `${sBase} rotate${axis}(${sign * 180}deg)`, opacity: 0 },
393
+ { transform: sBase, opacity: 1 },
394
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
395
+ const b = ctx.ghost.animate([{ transform: gBase, opacity: 1 }, { transform: `${gBase} rotate${axis}(${-sign * 180}deg)`, opacity: 0 }], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
396
+ await (0, util_1.runAll)([a, b], cleanup);
397
+ };
398
+ exports.rotate = rotate;
399
+ /** 绕画面外侧的轴转过去,像摩天轮的一格,不是原地翻面。 */
400
+ const ferris = async (ctx) => {
401
+ const toRight = String(ctx.params.dir ?? "left") === "right";
402
+ const { cleanup } = wrap3d(ctx);
403
+ const origin = toRight ? "120% 50%" : "-20% 50%";
404
+ const sign = toRight ? 1 : -1;
405
+ const prevS = ctx.stage.style.transformOrigin;
406
+ const prevG = ctx.ghost.style.transformOrigin;
407
+ ctx.stage.style.transformOrigin = origin;
408
+ ctx.ghost.style.transformOrigin = origin;
409
+ const sBase = ctx.stage.style.transform || "";
410
+ const gBase = ctx.ghost.style.transform || "";
411
+ const a = ctx.stage.animate([
412
+ { transform: `rotate(${sign * -180}deg) ${sBase}` },
413
+ { transform: `rotate(0deg) ${sBase}` },
414
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
415
+ const b = ctx.ghost.animate([
416
+ { transform: `rotate(0deg) ${gBase}` },
417
+ { transform: `rotate(${sign * 180}deg) ${gBase}` },
418
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
419
+ await (0, util_1.runAll)([a, b], () => {
420
+ ctx.stage.style.transformOrigin = prevS;
421
+ ctx.ghost.style.transformOrigin = prevG;
422
+ cleanup();
423
+ });
424
+ };
425
+ exports.ferris = ferris;
426
+ // [MS-PPTX] p15:prstTrans prst="peelOff":默认从右下角朝左揭起,invX(params.dir="right")朝右。
427
+ const peelOff = async (ctx) => {
428
+ const towardRight = String(ctx.params.dir ?? "left") === "right";
429
+ const { cleanup } = wrap3d(ctx);
430
+ const prevOrigin = ctx.ghost.style.transformOrigin;
431
+ const gBase = ctx.ghost.style.transform || "";
432
+ ctx.ghost.style.transformOrigin = towardRight ? "100% 0%" : "0% 0%";
433
+ const lift = towardRight ? 58 : -58;
434
+ const a = ctx.ghost.animate([
435
+ { clipPath: (0, geometry_1.peelFlatClip)(towardRight, 0), transform: gBase, opacity: 1 },
436
+ { clipPath: (0, geometry_1.peelFlatClip)(towardRight, 0.45), transform: `perspective(1100px) rotate(${lift * 0.35}deg) ${gBase}`, opacity: 1, offset: 0.45 },
437
+ { clipPath: (0, geometry_1.peelFlatClip)(towardRight, 1), transform: `perspective(1100px) rotateX(62deg) rotate(${lift}deg) ${gBase}`, opacity: 0 },
438
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
439
+ await (0, util_1.runAll)([a], () => {
440
+ ctx.ghost.style.clipPath = "";
441
+ ctx.ghost.style.transformOrigin = prevOrigin;
442
+ cleanup();
443
+ });
444
+ };
445
+ exports.peelOff = peelOff;
446
+ /** 卡片翻面:转到 90° 时只看见棱,没有旋转那种半透明交叠。 */
447
+ const flip = async (ctx) => {
448
+ const dir = String(ctx.params.dir ?? "left");
449
+ const { cleanup } = wrap3d(ctx);
450
+ const axis = dir === "up" || dir === "down" ? "X" : "Y";
451
+ const sign = dir === "right" || dir === "down" ? 1 : -1;
452
+ const sBase = ctx.stage.style.transform || "";
453
+ const gBase = ctx.ghost.style.transform || "";
454
+ ctx.stage.style.backfaceVisibility = "hidden";
455
+ ctx.ghost.style.backfaceVisibility = "hidden";
456
+ const a = ctx.stage.animate([
457
+ { transform: `${sBase} rotate${axis}(${sign * -180}deg)` },
458
+ { transform: sBase },
459
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
460
+ const b = ctx.ghost.animate([
461
+ { transform: gBase },
462
+ { transform: `${gBase} rotate${axis}(${sign * 180}deg)` },
463
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
464
+ await (0, util_1.runAll)([a, b], cleanup);
465
+ };
466
+ exports.flip = flip;
467
+ /** 两页左右错身换位,带一点倾斜。 */
468
+ const switchFx = async (ctx) => {
469
+ const toLeft = String(ctx.params.dir ?? "left") !== "right";
470
+ const sign = toLeft ? -1 : 1;
471
+ const w = ctx.stageSize.w;
472
+ const sBase = ctx.stage.style.transform || "";
473
+ const gBase = ctx.ghost.style.transform || "";
474
+ const a = ctx.stage.animate([
475
+ { transform: `translateX(${sign * w}px) rotate(${sign * -7}deg) ${sBase}` },
476
+ { transform: sBase },
477
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
478
+ const b = ctx.ghost.animate([
479
+ { transform: gBase },
480
+ { transform: `translateX(${-sign * w}px) rotate(${sign * 7}deg) ${gBase}` },
481
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
482
+ await (0, util_1.runAll)([a, b]);
483
+ };
484
+ exports.switchFx = switchFx;
485
+ const drape = async (ctx) => {
486
+ const toRight = String(ctx.params.dir ?? "left") === "right";
487
+ const sign = toRight ? 1 : -1;
488
+ const sBase = ctx.stage.style.transform || "";
489
+ const gBase = ctx.ghost.style.transform || "";
490
+ const ease = "cubic-bezier(0.16, 1, 0.3, 1)";
491
+ const a = ctx.stage.animate([{ transform: `${sBase} translateY(-100%)` }, { transform: sBase }], { duration: (0, util_1.ms)(ctx), easing: ease, fill: "forwards" });
492
+ const b = ctx.ghost.animate([
493
+ { transform: gBase, opacity: 1 },
494
+ { transform: `${gBase} translate(${sign * 28}%, 110%) rotate(${sign * 11}deg)`, opacity: 0 },
495
+ ], { duration: (0, util_1.ms)(ctx), easing: ease, fill: "forwards" });
496
+ await (0, util_1.runAll)([a, b]);
497
+ };
498
+ exports.drape = drape;
499
+ /** 帘幕:多条竖褶从中缝向两边收走。 */
500
+ const curtains = async (ctx) => {
501
+ const count = 8;
502
+ const folds = [];
503
+ for (let i = 0; i < count; i++) {
504
+ const left = (i / count) * 100;
505
+ const right = ((count - i - 1) / count) * 100;
506
+ folds.push((0, twin_1.mountTwin)(ctx.ghost, `inset(0 ${right}% 0 ${left}%)`));
507
+ }
508
+ const prev = ctx.ghost.style.visibility;
509
+ ctx.ghost.style.visibility = "hidden";
510
+ const dur = (0, util_1.ms)(ctx);
511
+ const anims = folds.map((el, i) => {
512
+ const toLeft = i < count / 2;
513
+ const base = el.style.transform || "";
514
+ return el.animate([
515
+ { transform: base, opacity: 1 },
516
+ { transform: `translateX(${toLeft ? -110 : 110}%) scaleX(0.35) ${base}`, opacity: 0.15 },
517
+ ], { duration: dur, easing: util_1.STD_EASE, fill: "forwards", delay: Math.abs(i - (count - 1) / 2) * dur * 0.04 });
518
+ });
519
+ await (0, util_1.runAll)(anims, () => (0, twin_1.dropTwins)(folds, ctx.ghost, prev));
520
+ };
521
+ exports.curtains = curtains;
522
+ const ripple = async (ctx) => {
523
+ const at = (0, geometry_1.rippleAt)(String(ctx.params.dir ?? ""));
524
+ const a = ctx.ghost.animate([
525
+ { clipPath: `circle(160% at ${at})`, opacity: 1 },
526
+ { clipPath: `circle(0% at ${at})`, opacity: 0 },
527
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
528
+ await (0, util_1.runAll)([a], () => {
529
+ ctx.ghost.style.clipPath = "";
530
+ });
531
+ };
532
+ exports.ripple = ripple;
533
+ const warp = async (ctx) => {
534
+ const out = String(ctx.params.dir ?? "in") === "out";
535
+ const gBase = ctx.ghost.style.transform || "";
536
+ const sBase = ctx.stage.style.transform || "";
537
+ if (out) {
538
+ const a = ctx.stage.animate([
539
+ { transform: `scale(0.08) skew(22deg) ${sBase}`, opacity: 0 },
540
+ { transform: sBase, opacity: 1 },
541
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
542
+ const b = ctx.ghost.animate([{ opacity: 1 }, { opacity: 0 }], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
543
+ await (0, util_1.runAll)([a, b]);
544
+ return;
545
+ }
546
+ const a = ctx.ghost.animate([
547
+ { transform: gBase, opacity: 1 },
548
+ { transform: `scale(0.08) skew(-22deg) ${gBase}`, opacity: 0 },
549
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
550
+ await (0, util_1.runAll)([a]);
551
+ };
552
+ exports.warp = warp;
553
+ const vortex = async (ctx) => {
554
+ const dir = String(ctx.params.dir ?? "left");
555
+ const gBase = ctx.ghost.style.transform || "";
556
+ const clockwise = dir === "right" || dir === "down";
557
+ const spin = clockwise ? 540 : -540;
558
+ const vertical = dir === "up" || dir === "down";
559
+ const turn = vertical ? `rotateX(${spin}deg)` : `rotate(${spin}deg)`;
560
+ const a = ctx.ghost.animate([
561
+ { transform: gBase, opacity: 1 },
562
+ { transform: `scale(0) ${turn} ${gBase}`, opacity: 0 },
563
+ ], { duration: (0, util_1.ms)(ctx), easing: util_1.STD_EASE, fill: "forwards" });
564
+ await (0, util_1.runAll)([a]);
565
+ };
566
+ exports.vortex = vortex;