@rxdrag/website-lib-core 0.0.102 → 0.0.104

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 (43) hide show
  1. package/package.json +7 -7
  2. package/src/component-logic/index.ts +1 -8
  3. package/src/global.d.ts +7 -0
  4. package/src/index.ts +1 -3
  5. package/src/react/components/BackgroundHlsVideoPlayer.tsx +30 -1
  6. package/src/react/components/ContactForm/ContactForm.tsx +3 -2
  7. package/src/react/components/ContactForm/types.ts +1 -0
  8. package/src/react/components/Scroller.tsx +32 -4
  9. package/src/react/components/Share/socials.tsx +1 -2
  10. package/src/react/components/all.ts +39 -0
  11. package/src/react/components/index.ts +1 -2
  12. package/src/react/index.ts +1 -2
  13. package/src/component-logic/collapse.ts +0 -61
  14. package/src/component-logic/gsap.d.ts +0 -4
  15. package/src/component-logic/modal.ts +0 -45
  16. package/src/component-logic/motion.ts +0 -272
  17. package/src/component-logic/number.ts +0 -45
  18. package/src/component-logic/popover.ts +0 -51
  19. package/src/component-logic/tabs.ts +0 -10
  20. package/src/controller/AnimateController.ts +0 -138
  21. package/src/controller/AosController.ts +0 -240
  22. package/src/controller/CollapseController.ts +0 -130
  23. package/src/controller/FlipController.ts +0 -339
  24. package/src/controller/ModalController.ts +0 -127
  25. package/src/controller/NumberController.ts +0 -161
  26. package/src/controller/OpenableController.ts +0 -367
  27. package/src/controller/PageLoader.ts +0 -154
  28. package/src/controller/PopoverController.ts +0 -116
  29. package/src/controller/TabsController.ts +0 -271
  30. package/src/controller/applyAnimation.ts +0 -86
  31. package/src/controller/applyInitialState.ts +0 -79
  32. package/src/controller/consts.ts +0 -33
  33. package/src/controller/index.ts +0 -10
  34. package/src/controller/utils.ts +0 -48
  35. package/src/motion/consts.ts +0 -428
  36. package/src/motion/convertToGsapVars.ts +0 -102
  37. package/src/motion/index.ts +0 -6
  38. package/src/motion/normalizeAnimation.ts +0 -28
  39. package/src/motion/normalizeAosAnimation.ts +0 -22
  40. package/src/motion/normalizePopupAnimation.ts +0 -24
  41. package/src/motion/types.ts +0 -133
  42. package/src/react/hooks/index.ts +0 -1
  43. package/src/react/hooks/useScroll.ts +0 -30
@@ -1,339 +0,0 @@
1
- import { DATA_MOTION_FLIP } from "../component-logic/tabs";
2
-
3
- // 全局 Flip 控制器
4
- export class FlipController {
5
- // 单例实例
6
- private static instances: Record<string, FlipController> = {};
7
- private constructor(private doc?: Document) {}
8
-
9
- // 记录元素的上一个位置(相对于文档的绝对位置)
10
- private previousPositions = new Map<
11
- string,
12
- {
13
- left: number;
14
- top: number;
15
- width: number;
16
- height: number;
17
- wasVisible?: boolean; // 标记元素是否曾经可见
18
- }
19
- >();
20
- // 标记正在处理的元素,防止递归循环
21
- private processingElements = new Set<HTMLElement>();
22
- // 观察器
23
- private resizeObserver: ResizeObserver | null = null;
24
-
25
- // 获取单例实例
26
- static getInstance(key: string = "runtime", doc?: Document): FlipController {
27
- if (!FlipController.instances[key]) {
28
- FlipController.instances[key] = new FlipController(doc);
29
- } else if (doc) {
30
- FlipController.instances[key].setDoc(doc);
31
- }
32
- return FlipController.instances[key];
33
- }
34
- // 初始化控制器
35
- mount() {
36
- this.unmount();
37
- // 设置观察器
38
- this.setupObservers();
39
- // 初始化元素
40
- this.initElements();
41
- }
42
-
43
- unmount() {
44
- // 移除观察器
45
- this.resizeObserver?.disconnect();
46
- this.resizeObserver = null;
47
- // 移除事件监听器
48
- this.doc?.defaultView?.removeEventListener("scroll", this.handleScroll);
49
- // 清理数据结构
50
- this.previousPositions.clear();
51
- this.processingElements.clear();
52
- }
53
-
54
- // 销毁指定实例
55
- static destroyInstance(key: string = "runtime") {
56
- if (FlipController.instances[key]) {
57
- FlipController.instances[key].unmount();
58
- delete FlipController.instances[key];
59
- }
60
- }
61
-
62
- // 销毁所有实例
63
- static destroyAllInstances() {
64
- Object.keys(FlipController.instances).forEach((key) => {
65
- FlipController.instances[key].unmount();
66
- });
67
- FlipController.instances = {};
68
- }
69
-
70
- private setDoc(doc?: Document) {
71
- this.doc = doc;
72
- }
73
-
74
- // 滚动事件处理函数
75
- private handleScroll = () => {
76
- // 页面滚动时,重新计算所有需要动画的元素
77
- this.doc
78
- ?.querySelectorAll<HTMLElement>("[data-flip]")
79
- .forEach((element) => {
80
- if (this.previousPositions.has(element.dataset.flip || "")) {
81
- this.performAnimation(element);
82
- }
83
- });
84
- };
85
-
86
- // 设置观察器
87
- private setupObservers() {
88
- // 设置 ResizeObserver 监控元素尺寸变化
89
- this.resizeObserver = new ResizeObserver((entries) => {
90
- entries.forEach((entry) => {
91
- if (
92
- this.doc?.defaultView?.HTMLElement &&
93
- entry.target instanceof this.doc?.defaultView?.HTMLElement
94
- ) {
95
- this.performAnimation(entry.target);
96
- }
97
- });
98
- });
99
-
100
- // 监听滚动事件 - 滚动不会影响相对于文档的绝对位置,所以不需要特殊处理
101
- this.doc?.defaultView?.addEventListener("scroll", this.handleScroll, {
102
- passive: true,
103
- });
104
- }
105
-
106
- // 初始化元素,记录初始位置
107
- private initElements() {
108
- // 查找所有带有 data-flip 属性的元素
109
- const flipElements = this.doc?.querySelectorAll<HTMLElement>("[data-flip]");
110
- flipElements?.forEach((element) => {
111
- // 添加到 ResizeObserver 监控
112
- if (this.resizeObserver) {
113
- this.resizeObserver.observe(element);
114
- }
115
- });
116
-
117
- // 添加 IntersectionObserver 来监测元素进入视窗
118
- if (typeof IntersectionObserver !== "undefined" && this.doc) {
119
- const intersectionObserver = new IntersectionObserver(
120
- (entries) => {
121
- entries.forEach((entry) => {
122
- if (entry.isIntersecting && entry.target instanceof HTMLElement) {
123
- const flipKey = entry.target.dataset.flip;
124
- if (flipKey) {
125
- const position = this.previousPositions.get(flipKey);
126
- if (position && position.wasVisible === false) {
127
- // 元素首次进入视窗,更新位置记录
128
- this.recordPosition(entry.target);
129
- }
130
- }
131
- }
132
- });
133
- },
134
- { threshold: 0.1 }
135
- );
136
-
137
- // 观察所有 flip 元素
138
- flipElements?.forEach((element) => {
139
- intersectionObserver.observe(element);
140
- });
141
- }
142
-
143
- // 延迟记录初始位置,确保元素已经渲染完成
144
- setTimeout(() => {
145
- // 记录每个元素的初始位置
146
- flipElements?.forEach((element) => {
147
- this.recordPosition(element);
148
- });
149
- }, 100); // 给予足够的时间让元素渲染完成
150
- }
151
-
152
- // 记录元素位置
153
- private recordPosition(element: HTMLElement) {
154
- const flipKey = element.dataset.flip;
155
- if (!flipKey) return;
156
-
157
- // 检查元素是否可见
158
- const isCurrentlyVisible = this.isVisible(element);
159
- // 获取之前的位置记录
160
- const previousPosition = this.previousPositions.get(flipKey);
161
-
162
- // 如果元素不可见且没有之前的记录,我们仍然记录它的位置
163
- // 但标记它为不可见,这样当它首次出现时我们可以决定是否应用动画
164
- if (!isCurrentlyVisible && !previousPosition) {
165
- // 继续执行,记录位置
166
- }
167
-
168
- // 获取元素相对于视口的位置
169
- const rect = element.getBoundingClientRect();
170
-
171
- // 计算元素相对于文档的绝对位置
172
- const absoluteLeft = rect.left + (this.doc?.defaultView?.scrollX || 0);
173
- const absoluteTop = rect.top + (this.doc?.defaultView?.scrollY || 0);
174
-
175
- // 记录元素相对于文档的绝对位置
176
- this.previousPositions.set(flipKey, {
177
- left: absoluteLeft,
178
- top: absoluteTop,
179
- width: rect.width,
180
- height: rect.height,
181
- wasVisible: isCurrentlyVisible, // 记录元素是否可见
182
- });
183
- }
184
-
185
- // 检查元素是否可见
186
- private isVisible(element: HTMLElement): boolean {
187
- const rect = element.getBoundingClientRect();
188
- return (
189
- rect.width > 0 &&
190
- rect.height > 0 &&
191
- rect.top < (this.doc?.defaultView?.innerHeight || 0) &&
192
- rect.bottom > 0 &&
193
- rect.left < (this.doc?.defaultView?.innerWidth || 0) &&
194
- rect.right > 0
195
- );
196
- }
197
-
198
- // 执行 FLIP 动画
199
- performAnimation(element: HTMLElement) {
200
- // 防止重复处理同一元素
201
- if (this.processingElements.has(element)) {
202
- return;
203
- }
204
-
205
- // 获取元素的 flip key
206
- const flipKey = element.dataset.flip;
207
- if (!flipKey) {
208
- return;
209
- }
210
-
211
- // 检查元素是否可见
212
- if (!this.isVisible(element)) {
213
- return;
214
- }
215
-
216
- // 记录当前正在处理的元素
217
- this.processingElements.add(element);
218
-
219
- // 获取元素的当前位置(相对于视口)
220
- const currentRect = element.getBoundingClientRect();
221
-
222
- // 检查当前位置是否有效
223
- if (currentRect.width === 0 || currentRect.height === 0) {
224
- this.processingElements.delete(element);
225
- return;
226
- }
227
-
228
- // 计算当前元素相对于文档的绝对位置
229
- const currentAbsoluteLeft =
230
- currentRect.left + (this.doc?.defaultView?.scrollX || 0);
231
- const currentAbsoluteTop =
232
- currentRect.top + (this.doc?.defaultView?.scrollY || 0);
233
-
234
- // 获取元素的上一个位置
235
- const previousPosition = this.previousPositions.get(flipKey);
236
-
237
- // 如果没有上一个位置,记录当前位置并返回
238
- if (!previousPosition) {
239
- this.recordPosition(element);
240
- this.processingElements.delete(element);
241
- return;
242
- }
243
-
244
- // 如果元素之前未曾可见(首次进入视窗),我们可能想要跳过动画
245
- // 或者应用特殊的入场动画
246
- if (this.isVisible(element) && previousPosition.wasVisible === false) {
247
- // 更新位置记录,标记为可见
248
- this.recordPosition(element);
249
- this.processingElements.delete(element);
250
- return;
251
- }
252
-
253
- // 检查上一个位置是否有效
254
- if (previousPosition.width === 0 || previousPosition.height === 0) {
255
- this.recordPosition(element);
256
- this.processingElements.delete(element);
257
- return;
258
- }
259
-
260
- // 计算位置差异(基于绝对位置,不需要考虑滚动)
261
- const deltaX = previousPosition.left - currentAbsoluteLeft;
262
- const deltaY = previousPosition.top - currentAbsoluteTop;
263
- const scaleX = previousPosition.width / currentRect.width;
264
- const scaleY = previousPosition.height / currentRect.height;
265
-
266
- // 如果位置没有变化,跳过动画
267
- if (
268
- Math.abs(deltaX) < 1 &&
269
- Math.abs(deltaY) < 1 &&
270
- Math.abs(scaleX - 1) < 0.01 &&
271
- Math.abs(scaleY - 1) < 0.01
272
- ) {
273
- this.recordPosition(element);
274
- this.processingElements.delete(element);
275
- return;
276
- }
277
-
278
- try {
279
- // 使用原生 Web Animation API 执行动画
280
-
281
- // 先将元素设置为初始位置
282
- element.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${scaleX}, ${scaleY})`;
283
- element.style.transformOrigin = "0 0";
284
-
285
- // 使用 requestAnimationFrame 确保初始状态已应用
286
- requestAnimationFrame(() => {
287
- const container = element.closest(`[${DATA_MOTION_FLIP}]`);
288
- const transionJSON = (container as HTMLElement | null)?.getAttribute(
289
- DATA_MOTION_FLIP
290
- );
291
- const transion = transionJSON
292
- ? JSON.parse(transionJSON)
293
- : {
294
- duration: 300,
295
- };
296
- // 创建动画
297
- const animation = element.animate(
298
- [
299
- {
300
- transform: `translate(${deltaX}px, ${deltaY}px) scale(${scaleX}, ${scaleY})`,
301
- },
302
- { transform: "translate(0, 0) scale(1, 1)" },
303
- ],
304
- transion
305
- );
306
-
307
- // 动画完成后的处理
308
- animation.onfinish = () => {
309
- // 清除样式并更新位置
310
- element.style.transform = "";
311
- element.style.transformOrigin = "";
312
- this.recordPosition(element);
313
- this.processingElements.delete(element);
314
- };
315
-
316
- // 设置一个备用的超时处理,以防动画事件不触发
317
- setTimeout(() => {
318
- if (this.processingElements.has(element)) {
319
- element.style.transform = "";
320
- element.style.transformOrigin = "";
321
- this.recordPosition(element);
322
- this.processingElements.delete(element);
323
- }
324
- }, 350); // 略长于动画时间
325
- });
326
- } catch (error) {
327
- // 出错时也要清理
328
- element.style.transform = "";
329
- element.style.transformOrigin = "";
330
- this.recordPosition(element);
331
- this.processingElements.delete(element);
332
- }
333
- }
334
- }
335
-
336
- export const flip = FlipController.getInstance(
337
- "runtime",
338
- typeof document !== "undefined" ? document : undefined
339
- );
@@ -1,127 +0,0 @@
1
- import { DATA_OPENABLE, DATA_OPENABLE_ROLE, OpenAble } from "./consts";
2
- import { OpenableController } from "./OpenableController";
3
-
4
- export class ModalController extends OpenableController {
5
- private static instances: Record<string, ModalController> = {};
6
-
7
- private constructor(doc?: Document) {
8
- super(doc);
9
- }
10
-
11
- public static getInstance(
12
- key: string = "runtime",
13
- doc?: Document
14
- ): ModalController {
15
- if (!ModalController.instances[key]) {
16
- ModalController.instances[key] = new ModalController(doc);
17
- } else if (doc) {
18
- ModalController.instances[key].setDoc(doc);
19
- }
20
- return ModalController.instances[key];
21
- }
22
-
23
- /**
24
- * 销毁指定 key 的实例
25
- * @param key 实例的唯一标识
26
- */
27
- public static destroyInstance(key: string = "runtime"): void {
28
- if (ModalController.instances[key]) {
29
- ModalController.instances[key].destroy();
30
- delete ModalController.instances[key];
31
- }
32
- }
33
-
34
- private setDoc(doc?: Document) {
35
- this.doc = doc;
36
- }
37
-
38
- mount() {
39
- super.mount();
40
- const popups = this.doc?.querySelectorAll(`[${DATA_OPENABLE}]`);
41
- popups?.forEach((popup) => {
42
- const openableKey = popup.getAttribute(DATA_OPENABLE);
43
- //处理鼠标交互事件
44
- if (openableKey && popup) {
45
- if (popup.getAttribute(DATA_OPENABLE_ROLE) === OpenAble.ModalTrigger) {
46
- const unsub = this.initModalTrigger(openableKey, popup as HTMLElement);
47
- this.unmountHandlers.push(unsub);
48
- }
49
- }
50
- //获取所有 ModalCloser 实例,并处理点击事件
51
- //TODO:未处理嵌套跟Modal外部关闭的情况
52
- const closers = popup?.querySelectorAll(
53
- `[${DATA_OPENABLE_ROLE}="${OpenAble.ModalCloser}"]`
54
- );
55
-
56
- closers?.forEach((closer) => {
57
- // 使用initModalCloser函数处理关闭按钮事件
58
- if (openableKey) {
59
- const unsub = this.initModalCloser(openableKey, closer as HTMLElement);
60
- this.unmountHandlers.push(unsub);
61
- }
62
- });
63
- });
64
- }
65
- /**
66
- * 初始化Modal的点击交互事件
67
- * @param openableKey - 弹出层的唯一标识
68
- * @param trigger - 触发器元素
69
- * @returns 清理函数,用于移除事件监听器
70
- */
71
- public initModalTrigger(openableKey: string, trigger: HTMLElement): () => void {
72
- if (!this.doc) return () => {};
73
-
74
- try {
75
- // 创建新的事件处理函数
76
- const handleTriggerClick = () => {
77
- // 查找 Modal 容器
78
- this.open(openableKey, trigger);
79
- };
80
-
81
- // 添加新的事件监听器
82
- trigger.addEventListener("click", handleTriggerClick);
83
-
84
- return () => {
85
- trigger.removeEventListener("click", handleTriggerClick);
86
- };
87
- } catch (error) {
88
- console.error(`初始化Modal事件失败: ${openableKey}`, error);
89
- return () => {};
90
- }
91
- }
92
-
93
- /**
94
- * 初始化Modal关闭按钮的点击交互事件
95
- * @param openableKey - 弹出层的唯一标识
96
- * @param closer - 关闭按钮元素
97
- * @returns 清理函数,用于移除事件监听器
98
- */
99
- public initModalCloser(openableKey: string, closer: HTMLElement): () => void {
100
- if (!this.doc) return () => {};
101
-
102
- try {
103
- // 创建新的事件处理函数
104
- const handleCloserClick = () => {
105
- this.close(openableKey);
106
- };
107
-
108
- // 添加新的事件监听器
109
- closer.addEventListener("click", handleCloserClick);
110
-
111
- return () => {
112
- closer.removeEventListener("click", handleCloserClick);
113
- };
114
- } catch (error) {
115
- console.error(`初始化Modal关闭按钮事件失败: ${openableKey}`, error);
116
- return () => {};
117
- }
118
- }
119
- unmount(): void {
120
- super.unmount();
121
- }
122
- }
123
-
124
- export const modal = ModalController.getInstance(
125
- "runtime",
126
- typeof document !== "undefined" ? document : undefined
127
- );
@@ -1,161 +0,0 @@
1
- import { DATA_MOTION_NUMBER, DATA_MOTION_NUMBER_ONCE } from "../motion/consts";
2
- import { gsap } from "gsap/dist/gsap";
3
-
4
- export class NumberController {
5
- private static instances: Record<string, NumberController> = {};
6
- private doc?: Document;
7
- private observers = new Map();
8
-
9
- private constructor(doc?: Document) {
10
- this.doc = doc;
11
- }
12
-
13
- /**
14
- * 获取 NumberController 实例
15
- * @param key 实例的唯一标识符
16
- * @param doc 文档对象
17
- * @returns NumberController 实例
18
- */
19
- public static getInstance(
20
- key: string = "runtime",
21
- doc?: Document
22
- ): NumberController {
23
- if (!NumberController.instances[key]) {
24
- NumberController.instances[key] = new NumberController(doc);
25
- } else if (doc) {
26
- NumberController.instances[key].setDoc(doc);
27
- }
28
- return NumberController.instances[key];
29
- }
30
-
31
- /**
32
- * 销毁指定 key 的实例
33
- * @param key 实例的唯一标识符
34
- */
35
- public static destroyInstance(key: string = "runtime"): void {
36
- if (NumberController.instances[key]) {
37
- delete NumberController.instances[key];
38
- }
39
- }
40
-
41
- /**
42
- * 设置文档对象
43
- * @param doc 文档对象
44
- */
45
- public setDoc(doc: Document): void {
46
- this.doc = doc;
47
- }
48
-
49
- mount() {
50
- // 清理之前的观察器
51
- this.unmount();
52
-
53
- // 检查文档对象是否存在
54
- if (!this.doc) {
55
- console.error("文档对象未设置,无法初始化数字动画");
56
- return;
57
- }
58
-
59
- // 使用正确的属性选择器
60
- const elements = this.doc.querySelectorAll(`[${DATA_MOTION_NUMBER}]`);
61
-
62
- elements?.forEach((element) => {
63
- if (
64
- this.doc?.defaultView?.HTMLElement &&
65
- element instanceof this.doc?.defaultView?.HTMLElement
66
- ) {
67
- const dataAttr = element.getAttribute(DATA_MOTION_NUMBER);
68
- if (dataAttr) {
69
- try {
70
- const data = JSON.parse(dataAttr);
71
- // 确保 start 和 end 是数字类型
72
- const startValue = Number(data.start);
73
- const endValue = Number(data.end);
74
- const transition = data.transition;
75
- const once =
76
- element.getAttribute(DATA_MOTION_NUMBER_ONCE) === "true";
77
-
78
- const numberElement = element.querySelector(
79
- ".motion-safe\\:animate-number"
80
- ) as HTMLElement;
81
- const signElement = element.querySelector(
82
- ".motion-safe\\:animate-sign"
83
- ) as HTMLElement;
84
-
85
- if (!numberElement) {
86
- return;
87
- }
88
-
89
- // 重置初始状态
90
- numberElement.innerHTML = startValue.toString();
91
- if (signElement) {
92
- signElement.style.display = "none";
93
- }
94
-
95
- // 使用 IntersectionObserver 监听元素进入视口
96
- const observer = new IntersectionObserver(
97
- (entries) => {
98
- if (entries[0].isIntersecting) {
99
- const aleadyRun =
100
- numberElement.dataset.animationNumberRun === "true";
101
- if (aleadyRun && once) {
102
- return;
103
- }
104
-
105
- numberElement.dataset.animationNumberRun = "true";
106
- // 使用 GSAP 动画替代 motion.animate
107
- const obj = { value: startValue };
108
- gsap.to(obj, {
109
- value: endValue,
110
- duration: transition?.duration || 1,
111
- ease: transition?.ease || "power2.out",
112
- onUpdate: () => {
113
- numberElement.innerHTML = Math.round(
114
- obj.value
115
- )?.toString();
116
- },
117
- onComplete: () => {
118
- if (signElement) {
119
- signElement.style.display = "inline";
120
- }
121
- },
122
- });
123
-
124
- if (once) {
125
- observer.disconnect();
126
- this.observers.delete(element);
127
- }
128
- } else if (!once) {
129
- numberElement.innerHTML = startValue.toString();
130
- if (signElement) {
131
- signElement.style.display = "none";
132
- console.log("隐藏符号元素");
133
- }
134
- }
135
- },
136
- { threshold: 0.1, rootMargin: "0px 0px -10% 0px" }
137
- );
138
-
139
- observer.observe(element);
140
- this.observers.set(element, observer);
141
- } catch (error) {
142
- console.error("处理数字动画时出错:", error);
143
- }
144
- }
145
- }
146
- });
147
- }
148
-
149
- public unmount() {
150
- this.observers.forEach((observer) => {
151
- observer.disconnect();
152
- });
153
- this.observers.clear();
154
- }
155
- }
156
-
157
- // 导出默认实例
158
- export const numberController = NumberController.getInstance(
159
- "runtime",
160
- typeof document !== "undefined" ? document : undefined
161
- );