@ray-js/components 1.7.56-beta.1 → 1.7.56-beta.10
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/lib/Animated/index.d.ts +14 -0
- package/lib/Animated/index.js +232 -73
- package/lib/Animated/native/ty-animated/index.js +133 -29
- package/lib/Animated/native/ty-animated/index.tyml +12 -6
- package/lib/Animated/types.d.ts +104 -45
- package/lib/Animated-old/animation.d.ts +47 -0
- package/lib/Animated-old/index.d.ts +21 -0
- package/lib/Animated-old/index.js +359 -0
- package/lib/Animated-old/native/ty-animated/index.d.ts +5 -0
- package/lib/Animated-old/native/ty-animated/index.js +208 -0
- package/lib/Animated-old/native/ty-animated/index.json +4 -0
- package/lib/Animated-old/native/ty-animated/index.tyml +26 -0
- package/lib/Animated-old/native/ty-animated/index.tyss +3 -0
- package/lib/Animated-old/types.d.ts +312 -0
- package/lib/Animated-old/types.js +1 -0
- package/lib/ScrollView/ScrollView.d.ts +1 -1
- package/lib/ScrollView/ScrollView.js +6 -9
- package/lib/ScrollView/ScrollView.thing.d.ts +1 -1
- package/lib/ScrollView/ScrollView.thing.js +2 -5
- package/package.json +5 -5
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animated - 纯净底层动画组件
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import type { AnimatedViewRef, AnimatedTextRef, AnimatedProps } from './types';
|
|
6
|
+
/**
|
|
7
|
+
* 默认导出
|
|
8
|
+
*/
|
|
9
|
+
declare const Animated: {
|
|
10
|
+
View: React.ForwardRefExoticComponent<Omit<AnimatedProps, "mode"> & React.RefAttributes<AnimatedViewRef>>;
|
|
11
|
+
Text: React.ForwardRefExoticComponent<Omit<AnimatedProps, "mode"> & React.RefAttributes<AnimatedTextRef>>;
|
|
12
|
+
};
|
|
13
|
+
export default Animated;
|
|
14
|
+
export type { AnimatedViewRef, AnimatedTextRef, AnimatedProps } from './types';
|
package/lib/Animated/index.js
CHANGED
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
|
-
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
|
|
3
|
-
const _excluded = ["finish"];
|
|
4
2
|
import "core-js/modules/es.regexp.exec.js";
|
|
5
3
|
import "core-js/modules/es.string.replace.js";
|
|
6
4
|
import "core-js/modules/esnext.iterator.constructor.js";
|
|
7
5
|
import "core-js/modules/esnext.iterator.for-each.js";
|
|
8
6
|
import "core-js/modules/esnext.iterator.map.js";
|
|
9
7
|
import "core-js/modules/web.dom-collections.iterator.js";
|
|
8
|
+
/* eslint-disable import/no-named-as-default */
|
|
10
9
|
/**
|
|
11
10
|
* Animated - 纯净底层动画组件
|
|
12
|
-
*
|
|
13
|
-
* 只提供三个核心方法:
|
|
14
|
-
* - setProperty(property, value): 设置动画属性
|
|
15
|
-
* - setStyle(style): 设置样式
|
|
16
|
-
* - setText(text): 设置文本内容
|
|
17
|
-
*
|
|
18
|
-
* 可以配合任何动画库使用(GSAP、Anime.js、Framer Motion 等)
|
|
19
11
|
*/
|
|
20
|
-
import React, { forwardRef, useImperativeHandle, useRef, useMemo, useCallback } from 'react';
|
|
12
|
+
import React, { forwardRef, useImperativeHandle, useRef, useMemo, useCallback, useEffect } from 'react';
|
|
21
13
|
import TyAnimatedComponent from './native/ty-animated';
|
|
22
14
|
|
|
23
15
|
// 小程序 API 声明
|
|
@@ -45,14 +37,40 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
45
37
|
mode = 'view',
|
|
46
38
|
onAnimationStart,
|
|
47
39
|
onAnimationEnd,
|
|
48
|
-
//
|
|
40
|
+
// 核心触摸事件
|
|
49
41
|
onClick,
|
|
50
42
|
onTouchStart,
|
|
51
|
-
onTouchEnd
|
|
43
|
+
onTouchEnd,
|
|
44
|
+
onTouchMove,
|
|
45
|
+
onTouchCancel,
|
|
46
|
+
onLongPress,
|
|
47
|
+
lockOnClick
|
|
48
|
+
// lockOnLongPress,
|
|
52
49
|
} = props;
|
|
53
|
-
|
|
50
|
+
|
|
51
|
+
// 使用队列管理多个 play() 的回调
|
|
52
|
+
// 因为基础库会触发多次 finish 事件(旧动画被打断 + 新动画完成)
|
|
53
|
+
// 当前动画的回调
|
|
54
|
+
// 同一时间只有一个动画在执行,新动画会打断旧动画
|
|
55
|
+
const currentCallbackRef = useRef(null);
|
|
54
56
|
const componentIdRef = useRef(id || "animated-".concat(Math.random().toString(36).slice(2, 11)));
|
|
55
57
|
|
|
58
|
+
// 组件卸载时清理未完成的动画 Promise
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
return () => {
|
|
61
|
+
const callback = currentCallbackRef.current;
|
|
62
|
+
if (callback) {
|
|
63
|
+
// console.log(`[Animated][${Date.now()}] 组件卸载,resolve 未完成的动画 Promise`)
|
|
64
|
+
callback({
|
|
65
|
+
detail: {
|
|
66
|
+
unmounted: true
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
currentCallbackRef.current = null;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
56
74
|
// 提取文本内容(当 mode='text' 时)
|
|
57
75
|
const textContent = useMemo(() => {
|
|
58
76
|
if (mode !== 'text' || !children) return undefined;
|
|
@@ -61,23 +79,24 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
61
79
|
}
|
|
62
80
|
return undefined;
|
|
63
81
|
}, [mode, children]);
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 获取 DOM 实例
|
|
85
|
+
*/
|
|
86
|
+
const getDomInstance = () => {
|
|
87
|
+
try {
|
|
88
|
+
var _getCurrentPages;
|
|
89
|
+
const pages = (_getCurrentPages = getCurrentPages) === null || _getCurrentPages === void 0 ? void 0 : _getCurrentPages();
|
|
90
|
+
if (!pages || pages.length === 0) return null;
|
|
91
|
+
const currentPage = pages[pages.length - 1];
|
|
92
|
+
if (!(currentPage !== null && currentPage !== void 0 && currentPage.selectComponent)) return null;
|
|
93
|
+
return currentPage.selectComponent("#".concat(componentIdRef.current));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error('[Animated] 获取组件实例失败:', error);
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
};
|
|
64
99
|
useImperativeHandle(ref, () => {
|
|
65
|
-
/**
|
|
66
|
-
* 获取 DOM 实例
|
|
67
|
-
*/
|
|
68
|
-
const getDomInstance = () => {
|
|
69
|
-
try {
|
|
70
|
-
var _getCurrentPages;
|
|
71
|
-
const pages = (_getCurrentPages = getCurrentPages) === null || _getCurrentPages === void 0 ? void 0 : _getCurrentPages();
|
|
72
|
-
if (!pages || pages.length === 0) return null;
|
|
73
|
-
const currentPage = pages[pages.length - 1];
|
|
74
|
-
if (!(currentPage !== null && currentPage !== void 0 && currentPage.selectComponent)) return null;
|
|
75
|
-
return currentPage.selectComponent("#".concat(componentIdRef.current));
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error('[Animated] 获取组件实例失败:', error);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
100
|
return {
|
|
82
101
|
/**
|
|
83
102
|
* 设置样式(直通通道)
|
|
@@ -85,16 +104,56 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
85
104
|
* @example
|
|
86
105
|
* ref.current.setStyle({ color: 'red', fontSize: '16px', border: '1px solid #ccc' })
|
|
87
106
|
*/
|
|
88
|
-
setStyle(styleValue) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
107
|
+
setStyle(styleValue, callback) {
|
|
108
|
+
return new Promise(resolve => {
|
|
109
|
+
var _inst$setCustomStyle;
|
|
110
|
+
const inst = getDomInstance();
|
|
111
|
+
if (!inst) {
|
|
112
|
+
throw new Error('No DOM instance found');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 防止传入 undefined 或空值
|
|
116
|
+
if (styleValue === undefined || styleValue === null) {
|
|
117
|
+
console.warn('styleValue is undefined or null');
|
|
118
|
+
resolve();
|
|
119
|
+
if (callback) callback();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const styleString = typeof styleValue === 'string' ? styleValue : styleToString(styleValue);
|
|
123
|
+
if (!styleString) {
|
|
124
|
+
console.warn('styleString is empty');
|
|
125
|
+
resolve();
|
|
126
|
+
if (callback) callback();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
92
129
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
130
|
+
// 如果有动画在执行,setStyle 会清空 transition,打断动画
|
|
131
|
+
// 主动 resolve 当前 Promise
|
|
132
|
+
const animCallback = currentCallbackRef.current;
|
|
133
|
+
if (animCallback) {
|
|
134
|
+
// console.log(`[Animated][${Date.now()}] setStyle 打断动画,resolve Promise`)
|
|
135
|
+
animCallback({
|
|
136
|
+
detail: {
|
|
137
|
+
interruptedBySetStyle: true
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
currentCallbackRef.current = null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// setData 完成后的回调
|
|
144
|
+
(_inst$setCustomStyle = inst.setCustomStyle) === null || _inst$setCustomStyle === void 0 || _inst$setCustomStyle.call(inst, styleString, () => {
|
|
145
|
+
// 调用用户回调
|
|
146
|
+
if (callback && typeof callback === 'function') {
|
|
147
|
+
try {
|
|
148
|
+
callback();
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('[Animated] setStyle callback error:', error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// resolve Promise
|
|
154
|
+
resolve();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
98
157
|
},
|
|
99
158
|
/**
|
|
100
159
|
* 设置文本内容(仅 mode='text' 时可用)
|
|
@@ -109,39 +168,81 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
109
168
|
}
|
|
110
169
|
const inst = getDomInstance();
|
|
111
170
|
if (!inst) {
|
|
112
|
-
|
|
113
|
-
return;
|
|
171
|
+
throw new Error('No DOM instance found');
|
|
114
172
|
}
|
|
115
173
|
(_inst$__applyText = inst.__applyText) === null || _inst$__applyText === void 0 || _inst$__applyText.call(inst, text);
|
|
116
174
|
},
|
|
117
175
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
176
|
+
* 创建动画对象
|
|
177
|
+
* 使用小程序原生动画 API,这是性能最优的动画方式
|
|
178
|
+
*
|
|
179
|
+
* @param options - 动画配置(duration, timingFunction, delay, transformOrigin)
|
|
180
|
+
* @returns Animation 实例,包含 play() 方法
|
|
120
181
|
*
|
|
121
182
|
* @example
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
183
|
+
* // 创建并执行动画
|
|
184
|
+
* const animation = ref.current.animate()
|
|
185
|
+
* animation.translateX(100).scale(1.2).step({ duration: 300 })
|
|
186
|
+
* animation.translateY(50).step({ duration: 200 })
|
|
187
|
+
* await animation.play()
|
|
188
|
+
* console.log('动画完成!')
|
|
189
|
+
*
|
|
190
|
+
* // 链式调用
|
|
191
|
+
* const animation = ref.current
|
|
192
|
+
* .animate({ transformOrigin: '50% 50%' })
|
|
193
|
+
* .translateX(100)
|
|
194
|
+
* .scale(1.2)
|
|
195
|
+
* .step({ duration: 300 })
|
|
196
|
+
* await animation.play()
|
|
197
|
+
*
|
|
198
|
+
* // 复用动画
|
|
199
|
+
* const slideIn = ref.current.animate().translateX(100).step({ duration: 300 })
|
|
200
|
+
* await slideIn.play() // 第一次
|
|
201
|
+
* await slideIn.play() // 第二次
|
|
125
202
|
*/
|
|
126
|
-
animate(
|
|
127
|
-
var _inst$
|
|
203
|
+
animate(options) {
|
|
204
|
+
var _inst$_getCreateAnima;
|
|
128
205
|
const inst = getDomInstance();
|
|
129
206
|
if (!inst) {
|
|
130
|
-
|
|
131
|
-
return;
|
|
207
|
+
throw new Error('No DOM instance found');
|
|
132
208
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
callbackRef.current = event => {
|
|
140
|
-
finish(event);
|
|
141
|
-
callbackRef.current = null;
|
|
142
|
-
};
|
|
209
|
+
|
|
210
|
+
// 获取 createAnimation 函数
|
|
211
|
+
const createAnimation = (_inst$_getCreateAnima = inst._getCreateAnimation) === null || _inst$_getCreateAnima === void 0 ? void 0 : _inst$_getCreateAnima.call(inst);
|
|
212
|
+
if (!createAnimation) {
|
|
213
|
+
console.error('[Animated] createAnimation 不可用');
|
|
214
|
+
return null;
|
|
143
215
|
}
|
|
144
|
-
|
|
216
|
+
|
|
217
|
+
// 创建小程序原生 Animation 实例
|
|
218
|
+
const animation = createAnimation(options);
|
|
219
|
+
|
|
220
|
+
// 扩展 play 方法
|
|
221
|
+
animation.play = () => {
|
|
222
|
+
return new Promise(resolve => {
|
|
223
|
+
var _inst$__applyAnimData;
|
|
224
|
+
// 如果有旧的 Promise,主动 resolve 它(被新动画打断)
|
|
225
|
+
const oldCallback = currentCallbackRef.current;
|
|
226
|
+
if (oldCallback) {
|
|
227
|
+
// console.log(`[Animated][${Date.now()}] 新动画开始,把之前的动画给中断掉`)
|
|
228
|
+
oldCallback({
|
|
229
|
+
detail: {
|
|
230
|
+
interrupted: true
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 保存新的 callback
|
|
236
|
+
currentCallbackRef.current = resolve;
|
|
237
|
+
|
|
238
|
+
// console.log(`[Animated][${Date.now()}] play() 调用`)
|
|
239
|
+
|
|
240
|
+
// 导出动画数据
|
|
241
|
+
const animationData = animation.export();
|
|
242
|
+
(_inst$__applyAnimData = inst.__applyAnimData) === null || _inst$__applyAnimData === void 0 || _inst$__applyAnimData.call(inst, animationData);
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
return animation;
|
|
145
246
|
},
|
|
146
247
|
/**
|
|
147
248
|
* 获取元素的布局位置和尺寸信息
|
|
@@ -159,7 +260,9 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
159
260
|
const query = ty.createSelectorQuery();
|
|
160
261
|
query.select("#".concat(componentIdRef.current)).boundingClientRect(rect => {
|
|
161
262
|
resolve(rect || null);
|
|
162
|
-
}).exec()
|
|
263
|
+
}).exec(() => {
|
|
264
|
+
// exec callback
|
|
265
|
+
});
|
|
163
266
|
} catch (error) {
|
|
164
267
|
console.error('[Animated] getBoundingClientRect 执行失败:', error);
|
|
165
268
|
resolve(null);
|
|
@@ -182,7 +285,9 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
182
285
|
const query = ty.createSelectorQuery();
|
|
183
286
|
query.select("#".concat(componentIdRef.current)).fields(fields, res => {
|
|
184
287
|
resolve(res || null);
|
|
185
|
-
}).exec()
|
|
288
|
+
}).exec(() => {
|
|
289
|
+
// exec callback
|
|
290
|
+
});
|
|
186
291
|
} catch (error) {
|
|
187
292
|
console.error('[Animated] getFields 执行失败:', error);
|
|
188
293
|
resolve(null);
|
|
@@ -219,7 +324,9 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
219
324
|
}
|
|
220
325
|
});
|
|
221
326
|
resolve(Object.keys(styles).length > 0 ? styles : null);
|
|
222
|
-
}).exec()
|
|
327
|
+
}).exec(() => {
|
|
328
|
+
// exec callback
|
|
329
|
+
});
|
|
223
330
|
} catch (error) {
|
|
224
331
|
console.error('[Animated] getComputedStyle 执行失败:', error);
|
|
225
332
|
resolve(null);
|
|
@@ -235,18 +342,66 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
235
342
|
return typeof style === 'object' ? styleToString(style) : style;
|
|
236
343
|
}, [style]);
|
|
237
344
|
const handleAnimationEnd = useCallback(event => {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
345
|
+
const callback = currentCallbackRef.current;
|
|
346
|
+
if (callback) {
|
|
347
|
+
callback(event);
|
|
348
|
+
currentCallbackRef.current = null;
|
|
241
349
|
}
|
|
242
350
|
if (onAnimationEnd) {
|
|
243
351
|
onAnimationEnd();
|
|
244
352
|
}
|
|
245
|
-
}, [onAnimationEnd
|
|
353
|
+
}, [onAnimationEnd]);
|
|
354
|
+
|
|
355
|
+
// 包装 onClick,如果启用了 lockOnClick
|
|
356
|
+
const wrappedOnClick = useMemo(() => {
|
|
357
|
+
if (!onClick) return undefined;
|
|
358
|
+
if (lockOnClick) {
|
|
359
|
+
// 声明式锁:包装 onClick,完成后通过 setData 触发解锁
|
|
360
|
+
return async event => {
|
|
361
|
+
Date.now(); // console.log(`[Animated.React][${startTime}] 🎯 onClick 开始执行`)
|
|
362
|
+
try {
|
|
363
|
+
await onClick(event);
|
|
364
|
+
// console.log(
|
|
365
|
+
// `[Animated.React][${Date.now()}] onClick 执行完成,耗时: ${Date.now() - startTime}ms`
|
|
366
|
+
// )
|
|
367
|
+
} catch (error) {
|
|
368
|
+
console.error("[Animated.React][".concat(Date.now(), "] onClick \u6267\u884C\u5931\u8D25:"), error);
|
|
369
|
+
throw error;
|
|
370
|
+
} finally {
|
|
371
|
+
// 等待事件执行完成,自动解锁
|
|
372
|
+
await new Promise(resolve => {
|
|
373
|
+
const inst = getDomInstance();
|
|
374
|
+
inst.__unlockEvent('click', resolve);
|
|
375
|
+
});
|
|
376
|
+
// console.log('click 已解锁')
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return onClick;
|
|
381
|
+
}, [onClick, lockOnClick]);
|
|
382
|
+
|
|
383
|
+
// 自动检测哪些事件需要加锁
|
|
384
|
+
const autoLockEvents = useMemo(() => {
|
|
385
|
+
const events = [];
|
|
386
|
+
|
|
387
|
+
// 通过 prop 声明
|
|
388
|
+
if (lockOnClick) {
|
|
389
|
+
events.push('click');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// if (events.length > 0) {
|
|
393
|
+
// console.log(`[Animated][${Date.now()}] 自动加锁事件:`, events)
|
|
394
|
+
// }
|
|
395
|
+
|
|
396
|
+
return events;
|
|
397
|
+
}, [onClick, lockOnClick]);
|
|
246
398
|
const eventBindings = {};
|
|
247
|
-
if (
|
|
399
|
+
if (wrappedOnClick) eventBindings['bind:click'] = wrappedOnClick;
|
|
248
400
|
if (onTouchStart) eventBindings['bind:touchstart'] = onTouchStart;
|
|
249
401
|
if (onTouchEnd) eventBindings['bind:touchend'] = onTouchEnd;
|
|
402
|
+
if (onTouchMove) eventBindings['bind:touchmove'] = onTouchMove;
|
|
403
|
+
if (onTouchCancel) eventBindings['bind:touchcancel'] = onTouchCancel;
|
|
404
|
+
if (onLongPress) eventBindings['bind:longpress'] = onLongPress;
|
|
250
405
|
if (onAnimationStart) eventBindings['bind:animationbegin'] = onAnimationStart;
|
|
251
406
|
eventBindings['bind:animationfinish'] = handleAnimationEnd;
|
|
252
407
|
return (
|
|
@@ -255,12 +410,16 @@ const AnimatedBase = /*#__PURE__*/forwardRef((props, ref) => {
|
|
|
255
410
|
React.createElement(TyAnimatedComponent, _extends({
|
|
256
411
|
id: componentIdRef.current,
|
|
257
412
|
__mode: mode,
|
|
258
|
-
|
|
259
|
-
},
|
|
260
|
-
|
|
413
|
+
__componentId: componentIdRef.current
|
|
414
|
+
}, className ? {
|
|
415
|
+
__className: className
|
|
416
|
+
} : {}, customStyleValue ? {
|
|
417
|
+
__style: customStyleValue
|
|
261
418
|
} : {}, mode === 'text' && textContent !== undefined ? {
|
|
262
419
|
__text: textContent
|
|
263
|
-
} : {},
|
|
420
|
+
} : {}, autoLockEvents ? {
|
|
421
|
+
__autoLockEvents: autoLockEvents
|
|
422
|
+
} : [], eventBindings), mode === 'view' ? children : null)
|
|
264
423
|
);
|
|
265
424
|
});
|
|
266
425
|
AnimatedBase.displayName = 'AnimatedBase';
|
|
@@ -292,4 +451,4 @@ const Animated = {
|
|
|
292
451
|
};
|
|
293
452
|
export default Animated;
|
|
294
453
|
|
|
295
|
-
//
|
|
454
|
+
// 导出类型和工具函数
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
|
+
import "core-js/modules/es.string.trim.js";
|
|
3
|
+
import "core-js/modules/esnext.iterator.constructor.js";
|
|
4
|
+
import "core-js/modules/esnext.iterator.filter.js";
|
|
5
|
+
import "core-js/modules/esnext.iterator.for-each.js";
|
|
6
|
+
import "core-js/modules/esnext.iterator.map.js";
|
|
7
|
+
import "core-js/modules/web.dom-collections.iterator.js";
|
|
1
8
|
// @ts-nocheck
|
|
2
9
|
/**
|
|
3
10
|
* TyAnimated - 高性能动画组件(小程序原生层)
|
|
@@ -22,25 +29,6 @@ Component({
|
|
|
22
29
|
type: String,
|
|
23
30
|
value: ''
|
|
24
31
|
},
|
|
25
|
-
// 兼容 FastView 接口
|
|
26
|
-
customStyle: {
|
|
27
|
-
type: String,
|
|
28
|
-
value: '',
|
|
29
|
-
observer: function (v) {
|
|
30
|
-
this.setData({
|
|
31
|
-
__style: v
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
customClass: {
|
|
36
|
-
type: String,
|
|
37
|
-
value: '',
|
|
38
|
-
observer: function (v) {
|
|
39
|
-
this.setData({
|
|
40
|
-
__className: v
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
32
|
onAnimationStart: {
|
|
45
33
|
type: null,
|
|
46
34
|
value: null
|
|
@@ -61,20 +49,40 @@ Component({
|
|
|
61
49
|
},
|
|
62
50
|
lifetimes: {
|
|
63
51
|
attached() {
|
|
64
|
-
//
|
|
52
|
+
// 初始化
|
|
53
|
+
// 为每个事件类型维护独立的锁
|
|
54
|
+
this._locks = {};
|
|
55
|
+
this._componentId = this.data.__componentId || this.id;
|
|
56
|
+
this._excludedTouchStartIdentifier = new Set();
|
|
65
57
|
},
|
|
66
58
|
detached() {
|
|
67
|
-
//
|
|
59
|
+
//
|
|
68
60
|
}
|
|
69
61
|
},
|
|
70
62
|
methods: {
|
|
63
|
+
/**
|
|
64
|
+
* 原生层事件锁:解锁(支持指定事件类型)
|
|
65
|
+
* @param eventType - 事件类型,如 'click', 'longPress'
|
|
66
|
+
* @param resolve - Promise resolve 函数
|
|
67
|
+
*/
|
|
68
|
+
__unlockEvent(eventType, resolve) {
|
|
69
|
+
if (!eventType) {
|
|
70
|
+
console.warn('[TyAnimated] __unlockEvent: eventType is required');
|
|
71
|
+
resolve();
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 清除内部事件锁状态
|
|
76
|
+
this._locks[eventType] = false;
|
|
77
|
+
resolve();
|
|
78
|
+
},
|
|
71
79
|
/**
|
|
72
80
|
* 事件处理器
|
|
73
81
|
*/
|
|
74
82
|
__handleTiggerEvent(eventName, e) {
|
|
75
83
|
// 触发自定义事件,让外部可以监听
|
|
76
84
|
this.triggerEvent(eventName, e, {
|
|
77
|
-
|
|
85
|
+
bubbles: false,
|
|
78
86
|
composed: true
|
|
79
87
|
});
|
|
80
88
|
},
|
|
@@ -106,15 +114,55 @@ Component({
|
|
|
106
114
|
}
|
|
107
115
|
return null;
|
|
108
116
|
},
|
|
109
|
-
|
|
117
|
+
/**
|
|
118
|
+
* 解析样式字符串为对象
|
|
119
|
+
*/
|
|
120
|
+
_parseStyle(styleString) {
|
|
121
|
+
if (!styleString) return {};
|
|
122
|
+
const result = {};
|
|
123
|
+
const styles = styleString.split(';').filter(Boolean);
|
|
124
|
+
styles.forEach(style => {
|
|
125
|
+
const colonIndex = style.indexOf(':');
|
|
126
|
+
if (colonIndex > 0) {
|
|
127
|
+
const key = style.substring(0, colonIndex).trim();
|
|
128
|
+
const value = style.substring(colonIndex + 1).trim();
|
|
129
|
+
if (key && value) {
|
|
130
|
+
result[key] = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return result;
|
|
135
|
+
},
|
|
136
|
+
/**
|
|
137
|
+
* 样式对象转字符串
|
|
138
|
+
*/
|
|
139
|
+
_styleToString(styleObj) {
|
|
140
|
+
return Object.entries(styleObj).map(_ref => {
|
|
141
|
+
let [key, value] = _ref;
|
|
142
|
+
return "".concat(key, ":").concat(value);
|
|
143
|
+
}).join(';');
|
|
144
|
+
},
|
|
145
|
+
setCustomStyle(value, callback) {
|
|
110
146
|
// 防止传入 undefined 或 null 导致小程序报错
|
|
111
147
|
if (value === undefined || value === null) {
|
|
112
148
|
console.warn('[TyAnimatedComponent] setCustomStyle: value is undefined or null, ignored');
|
|
149
|
+
if (callback) callback();
|
|
113
150
|
return;
|
|
114
151
|
}
|
|
152
|
+
|
|
153
|
+
// 合并样式
|
|
154
|
+
const currentStyle = this._parseStyle(this.data.__style || '');
|
|
155
|
+
const newStyle = this._parseStyle(value);
|
|
156
|
+
const mergedStyle = _objectSpread(_objectSpread({}, currentStyle), newStyle);
|
|
157
|
+
let mergedStyleString = this._styleToString(mergedStyle);
|
|
158
|
+
if (mergedStyleString === this.data.__style && this.data.__animData !== null) {
|
|
159
|
+
// 添加一个不影响渲染的注释,使样式字符串不同
|
|
160
|
+
mergedStyleString = "".concat(mergedStyleString, ";/* ").concat(Date.now(), " */");
|
|
161
|
+
}
|
|
115
162
|
this.setData({
|
|
116
|
-
__style:
|
|
117
|
-
|
|
163
|
+
__style: mergedStyleString
|
|
164
|
+
}, () => {
|
|
165
|
+
if (callback) callback();
|
|
118
166
|
});
|
|
119
167
|
},
|
|
120
168
|
/**
|
|
@@ -127,6 +175,15 @@ Component({
|
|
|
127
175
|
});
|
|
128
176
|
}
|
|
129
177
|
},
|
|
178
|
+
__applyAnimData(animData) {
|
|
179
|
+
if (!animData) {
|
|
180
|
+
console.warn('[TyAnimatedComponent] __applyAnimData: animData is null or undefined');
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
this.setData({
|
|
184
|
+
__animData: animData
|
|
185
|
+
});
|
|
186
|
+
},
|
|
130
187
|
/**
|
|
131
188
|
* 核心方法:直接应用 Animation 对象
|
|
132
189
|
* @param {Function} fn - 接收 Animation 实例的回调函数
|
|
@@ -155,15 +212,62 @@ Component({
|
|
|
155
212
|
__animData: animData
|
|
156
213
|
});
|
|
157
214
|
},
|
|
158
|
-
// 核心触摸事件处理(只保留最常用的)
|
|
159
|
-
__onClick(e) {
|
|
160
|
-
this.__handleTiggerEvent('click', e);
|
|
161
|
-
},
|
|
162
215
|
__onTouchStart(e) {
|
|
216
|
+
var _this$_locks;
|
|
217
|
+
/**
|
|
218
|
+
* 因为end事件会合成tap事件的原因,可能会出现
|
|
219
|
+
* click -> lock -> 执行内部逻辑 ---- 过程中可能会有touchstart事件(identifier: 1) -> unlock -> click完成 -> touchend(identifier: 1) -> 合成了tap -> click(额外的异常点击)
|
|
220
|
+
* 逻辑层的锁 锁不住视图层的事件监听
|
|
221
|
+
*
|
|
222
|
+
* 方案:将在click事件锁住后的所有touchStart保存起来,在tap的时候对比set中是否有相同的序列id,有的话就直接return,并重置set
|
|
223
|
+
*/
|
|
224
|
+
|
|
225
|
+
// 只有在锁住的状态下存储 touchStart 的序列值
|
|
226
|
+
if ((_this$_locks = this._locks) !== null && _this$_locks !== void 0 && _this$_locks['click']) {
|
|
227
|
+
const touchId = e.touches[0].identifier; // 每个触摸序列的唯一ID
|
|
228
|
+
// console.log('------1-1--1-1-1-1-:::', touchId)
|
|
229
|
+
this._excludedTouchStartIdentifier.add(touchId);
|
|
230
|
+
}
|
|
163
231
|
this.__handleTiggerEvent('touchstart', e);
|
|
164
232
|
},
|
|
233
|
+
__onTouchMove(e) {
|
|
234
|
+
this.__handleTiggerEvent('touchmove', e);
|
|
235
|
+
},
|
|
165
236
|
__onTouchEnd(e) {
|
|
237
|
+
// console.log('=====2---22-2-2-::::', e.changedTouches[0].identifier)
|
|
238
|
+
|
|
166
239
|
this.__handleTiggerEvent('touchend', e);
|
|
240
|
+
},
|
|
241
|
+
__onTouchCancel(e) {
|
|
242
|
+
this.__handleTiggerEvent('touchcancel', e);
|
|
243
|
+
},
|
|
244
|
+
__onLongPress(e) {
|
|
245
|
+
this.__handleTiggerEvent('longpress', e);
|
|
246
|
+
},
|
|
247
|
+
// 核心触摸事件处理(只保留最常用的)
|
|
248
|
+
__onClick(e) {
|
|
249
|
+
var _this$_excludedTouchS, _this$_locks2;
|
|
250
|
+
Date.now();
|
|
251
|
+
const eventType = 'click';
|
|
252
|
+
// console.log('---3----3-3--:::', e.changedTouches[0].identifier)
|
|
253
|
+
// 对比set中是否有相同的序列id,有的话就直接return,并重置set
|
|
254
|
+
e.changedTouches[0].identifier;
|
|
255
|
+
const isExceptiontrigger = (_this$_excludedTouchS = this._excludedTouchStartIdentifier) === null || _this$_excludedTouchS === void 0 ? void 0 : _this$_excludedTouchS.has(e.changedTouches[0].identifier);
|
|
256
|
+
if (isExceptiontrigger) {
|
|
257
|
+
this._excludedTouchStartIdentifier.clear();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 检查该事件类型是否已加锁,锁住直接return
|
|
262
|
+
if ((_this$_locks2 = this._locks) !== null && _this$_locks2 !== void 0 && _this$_locks2[eventType]) {
|
|
263
|
+
// console.log(`[TyAnimated][${now}] ⚠️ 事件被锁拦截: ${eventType}`)
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// 如果发现click事件需要自动开解锁,在第一次click的时候就自动加上锁
|
|
267
|
+
if (this.data.__autoLockEvents.includes(eventType)) {
|
|
268
|
+
this._locks[eventType] = true;
|
|
269
|
+
}
|
|
270
|
+
this.__handleTiggerEvent(eventType, e);
|
|
167
271
|
}
|
|
168
272
|
}
|
|
169
273
|
});
|
|
@@ -2,24 +2,30 @@
|
|
|
2
2
|
<view
|
|
3
3
|
wx:if="{{__mode==='view'}}"
|
|
4
4
|
id="root"
|
|
5
|
-
class="
|
|
6
|
-
style="{{__style}}
|
|
5
|
+
class="{{__className}}"
|
|
6
|
+
style="{{__style}}"
|
|
7
7
|
animation="{{__animData}}"
|
|
8
|
-
bindtap="__onClick"
|
|
9
8
|
bindtouchstart="__onTouchStart"
|
|
10
9
|
bindtouchend="__onTouchEnd"
|
|
10
|
+
bindtouchmove="__onTouchMove"
|
|
11
|
+
bindtouchcancel="__onTouchCancel"
|
|
12
|
+
binglongpress="__onLongPress"
|
|
13
|
+
bindtap="__onClick"
|
|
11
14
|
>
|
|
12
15
|
<slot></slot>
|
|
13
16
|
</view>
|
|
14
17
|
<text
|
|
15
18
|
wx:else
|
|
16
19
|
id="rootText"
|
|
17
|
-
class="container {{__className}}
|
|
18
|
-
style="{{__style}}
|
|
20
|
+
class="container {{__className}}"
|
|
21
|
+
style="{{__style}}"
|
|
19
22
|
animation="{{__animData}}"
|
|
20
|
-
bindtap="__onClick"
|
|
21
23
|
bindtouchstart="__onTouchStart"
|
|
22
24
|
bindtouchend="__onTouchEnd"
|
|
25
|
+
bindtouchmove="__onTouchMove"
|
|
26
|
+
bindtouchcancel="__onTouchCancel"
|
|
27
|
+
binglongpress="__onLongPress"
|
|
28
|
+
bindtap="__onClick"
|
|
23
29
|
>{{__text}}</text>
|
|
24
30
|
</block>
|
|
25
31
|
|