pixuireactcomponents 1.5.59 → 1.5.60

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixuireactcomponents",
3
- "version": "1.5.59",
3
+ "version": "1.5.60",
4
4
  "description": "pixui react components",
5
5
  "main": "index.js",
6
6
  "sideEffects": false,
@@ -1,28 +1,58 @@
1
1
  import { h } from 'preact';
2
2
  /**
3
+ * 帧动画组件
3
4
  *
4
5
  * @param rootId 根节点id
5
6
  * @param rootClassName 根节点className
6
7
  * @param totalTime 动画总时长
7
8
  * @param width 组件宽度
8
9
  * @param height 组件高度
9
- * @param srcArr 图片url数组
10
+ * @param srcArr 图片 url 数组
10
11
  * @param onShow 每一帧显示时的回调
11
12
  * @param onFinish 动画结束时的回调
12
- * @param preloadCount 预加载节点的数量
13
+ * @param preloadCount 预加载节点的数量 (最少 3)
13
14
  * @param loop 是否循环播放
14
15
  * @param showLastFrame 播放结束后是否显示最后一帧
16
+ * @param cRef 外部组件引用(暴露 seek(frameIndex:number) 方法)
15
17
  */
16
- export declare let FrameAnimation: (props: {
17
- rootId?: string | undefined;
18
- rootClassName?: string | undefined;
18
+ export interface FrameAnimationHandle {
19
+ /** 跳转到指定帧(从 0 开始计数) */
20
+ seek: (frameIndex: number) => void;
21
+ /** 开始播放(从当前已显示帧继续) */
22
+ play: () => void;
23
+ /** 暂停播放(不清理预加载) */
24
+ pause: () => void;
25
+ }
26
+ export interface FrameAnimationProps {
27
+ rootId?: string;
28
+ rootClassName?: string;
29
+ /** 动画总时长(毫秒) */
19
30
  totalTime: number;
31
+ /** 组件宽度,如 "500px" */
20
32
  width: string;
33
+ /** 组件高度,如 "300px" */
21
34
  height: string;
22
- srcArr: string[];
23
- onShow?: ((nowframe: number) => void) | undefined;
24
- onFinish?: (() => void) | undefined;
25
- preloadCount?: number | undefined;
26
- loop?: boolean | undefined;
27
- showLastFrame?: boolean | undefined;
28
- }) => h.JSX.Element;
35
+ /** 每帧图片 URL 数组(与 spriteClassArr 二选一) */
36
+ srcArr?: string[];
37
+ /** 每帧对应的样式类名数组(className),在样式中指定 background-image */
38
+ spriteClassArr?: string[];
39
+ /** 每一帧显示的回调 */
40
+ onShow?: (nowframe: number) => void;
41
+ /** 动画结束回调 */
42
+ onFinish?: () => void;
43
+ /** 预加载节点数量,最少 3 */
44
+ preloadCount?: number;
45
+ /** 是否循环播放 */
46
+ loop?: boolean;
47
+ /** 播放结束后是否显示最后一帧 */
48
+ showLastFrame?: boolean;
49
+ /** 播放范围起始帧 (包含) */
50
+ rangeStart?: number;
51
+ /** 播放范围结束帧 (包含) */
52
+ rangeEnd?: number;
53
+ /** 外部组件引用,用于调用 seek 方法 */
54
+ cRef?: any;
55
+ /** 是否在初始化后自动播放(默认 true) */
56
+ autoplay?: boolean;
57
+ }
58
+ export declare const FrameAnimation: (props: FrameAnimationProps) => h.JSX.Element;
@@ -1,96 +1,255 @@
1
1
  import { useEffect, useRef } from 'preact/hooks';
2
2
  import { createRef, h } from 'preact';
3
- // let requestAnimationFrame = (f) => {
4
- // return setTimeout(f, 300);
5
- // };
6
- /**
7
- *
8
- * @param rootId 根节点id
9
- * @param rootClassName 根节点className
10
- * @param totalTime 动画总时长
11
- * @param width 组件宽度
12
- * @param height 组件高度
13
- * @param srcArr 图片url数组
14
- * @param onShow 每一帧显示时的回调
15
- * @param onFinish 动画结束时的回调
16
- * @param preloadCount 预加载节点的数量
17
- * @param loop 是否循环播放
18
- * @param showLastFrame 播放结束后是否显示最后一帧
19
- */
20
3
  export var FrameAnimation = function (props) {
21
- var _a, _b;
4
+ // ------------------------------ 参数与前置处理 ------------------------------
22
5
  props.preloadCount = Math.max(props.preloadCount || 3, 3);
23
- var totalTime = props.totalTime, width = props.width, height = props.height, srcArr = props.srcArr, onShow = props.onShow, preloadCount = props.preloadCount, loop = (_a = props.loop, _a === void 0 ? false : _a), onFinish = props.onFinish, showLastFrame = (_b = props.showLastFrame, _b === void 0 ? true : _b), rootId = props.rootId, rootClassName = props.rootClassName;
6
+ var totalTime = props.totalTime, width = props.width, height = props.height, _a = props.srcArr, srcArr = _a === void 0 ? [] : _a, spriteClassArr = props.spriteClassArr, onShow = props.onShow, preloadCount = props.preloadCount, _b = props.loop, loop = _b === void 0 ? false : _b, onFinish = props.onFinish, _c = props.showLastFrame, showLastFrame = _c === void 0 ? true : _c, rootId = props.rootId, rootClassName = props.rootClassName, rangeStart = props.rangeStart, rangeEnd = props.rangeEnd, cRef = props.cRef, _d = props.autoplay, autoplay = _d === void 0 ? true : _d;
7
+ // 模式判定:sprite 模式或图片模式
8
+ var useSprite = Array.isArray(spriteClassArr) && spriteClassArr.length > 0;
9
+ var frameCount = useSprite ? spriteClassArr.length : srcArr.length;
10
+ // 每帧耗时(保护除零)
11
+ var frameTime = frameCount > 0 ? totalTime / frameCount : 0;
12
+ // ------------------------------ 播放范围处理 ------------------------------
13
+ var startIndex = Math.max(0, Math.min(rangeStart != null ? rangeStart : 0, frameCount - 1));
14
+ var endIndex = Math.max(startIndex, Math.min(rangeEnd != null ? rangeEnd : frameCount - 1, frameCount - 1));
15
+ var rangeLen = endIndex - startIndex + 1;
16
+ // ------------------------------ DOM Ref 初始化 ------------------------------
24
17
  var cRefArr = useRef(Array.from({ length: preloadCount }, function () { return createRef(); })).current;
25
- var imgEleArr = useRef(Array.from({ length: preloadCount }, function (v, i) {
26
- return h("img", { ref: cRefArr[i], src: srcArr[i], style: { position: 'absolute', visibility: 'hidden', width: width, height: height } });
27
- })).current;
18
+ var imgEleArr = useRef(Array.from({ length: preloadCount }, function (_, i) { return (h("img", { ref: cRefArr[i], src: srcArr[i], style: { position: 'absolute', visibility: 'hidden', width: width, height: height } })); })).current;
19
+ // sprite 模式所需的 div 预加载节点
20
+ var dRefArr = useRef(Array.from({ length: preloadCount }, function () { return createRef(); })).current;
21
+ var spriteEleArr = useRef(Array.from({ length: preloadCount }, function (_, i) { return (h("div", { ref: dRefArr[i], style: { position: 'absolute', visibility: 'hidden', width: width, height: height } })); })).current;
22
+ // ------------------------------ 状态引用 ------------------------------
23
+ var startTimeRef = useRef(0); // 当前动画起始时间
24
+ var showedFrameRef = useRef(0); // 已经展示的帧序号
25
+ var isPlayingRef = useRef(autoplay !== false); // 播放状态(仅初始化受 autoplay 影响)
26
+ var rootRef = useRef(null); // 根容器,用于判断是否真的卸载
27
+ // ------------------------------ seek 功能实现 ------------------------------
28
+ var seek = function (frameIndex) {
29
+ console.log('frame seek !!', frameIndex);
30
+ if (frameCount <= 0)
31
+ return;
32
+ if (frameIndex < 0 || frameIndex >= frameCount)
33
+ return;
34
+ // 1. 直接更新可见帧及预加载
35
+ var bufferLen = useSprite ? dRefArr.length : cRefArr.length;
36
+ var nowArrIndex = bufferLen > 0 ? frameIndex % bufferLen : 0;
37
+ if (useSprite) {
38
+ dRefArr.forEach(function (ref, i) {
39
+ var divNode = ref.current;
40
+ if (!divNode)
41
+ return;
42
+ if (i === nowArrIndex) {
43
+ divNode.style.visibility = 'visible';
44
+ divNode.className = spriteClassArr[frameIndex];
45
+ }
46
+ else {
47
+ divNode.style.visibility = 'hidden';
48
+ var offset = (i + dRefArr.length - nowArrIndex) % dRefArr.length;
49
+ var offsetFrame = startIndex + ((frameIndex - startIndex + offset) % rangeLen);
50
+ divNode.className = spriteClassArr[offsetFrame];
51
+ }
52
+ });
53
+ }
54
+ else {
55
+ cRefArr.forEach(function (ref, i) {
56
+ var imgNode = ref.current;
57
+ if (!imgNode)
58
+ return;
59
+ if (i === nowArrIndex) {
60
+ imgNode.style.visibility = 'visible';
61
+ imgNode.src = srcArr[frameIndex];
62
+ }
63
+ else {
64
+ imgNode.style.visibility = 'hidden';
65
+ var offset = (i + cRefArr.length - nowArrIndex) % cRefArr.length;
66
+ var offsetFrame = startIndex + ((frameIndex - startIndex + offset) % rangeLen);
67
+ imgNode.src = srcArr[offsetFrame];
68
+ }
69
+ });
70
+ }
71
+ // 2. 回调 & 状态同步
72
+ showedFrameRef.current = frameIndex;
73
+ onShow && onShow(frameIndex);
74
+ // 3. 调整起始时间,让主循环从下一帧继续
75
+ startTimeRef.current = Date.now() - frameIndex * frameTime;
76
+ if (isPlayingRef.current) {
77
+ nextFrame(startTimeRef.current, frameIndex);
78
+ }
79
+ };
80
+ // 开始/暂停控制
81
+ var play = function () {
82
+ if (isPlayingRef.current)
83
+ return;
84
+ isPlayingRef.current = true;
85
+ // 从当前帧继续播放
86
+ startTimeRef.current = Date.now() - showedFrameRef.current * frameTime;
87
+ nextFrame(startTimeRef.current, showedFrameRef.current);
88
+ };
89
+ var pause = function () {
90
+ // 不清理任何节点或预加载,仅停止后续调度
91
+ isPlayingRef.current = false;
92
+ };
93
+ // 对外暴露方法
94
+ if (cRef) {
95
+ cRef.current = {
96
+ seek: seek,
97
+ play: play,
98
+ pause: pause,
99
+ };
100
+ }
101
+ // ------------------------------ 主循环初始化 ------------------------------
28
102
  useEffect(function () {
29
- var startTime = Date.now();
30
- var onrFrameTime = totalTime / srcArr.length;
31
- //设置第一帧
32
- cRefArr[0].current.style.visibility = 'visible';
33
- nextFrame(startTime, onrFrameTime, 0);
34
- }, []);
35
- //循环修改imgEleArr的src和visibility实现帧动画,,记录上一帧的序号防止跳帧
36
- var nextFrame = function (startTime, v, showedFrame) {
37
- var nowTime = Date.now();
38
- //该显示第几帧
39
- var nowFrame = Math.floor((nowTime - startTime) / v);
40
- //使用第几个img元素
41
- var nowArrIndex = nowFrame % preloadCount;
42
- var showedArrIndex = showedFrame % preloadCount;
43
- // console.log('nextFrame', nowFrame, 'nowArrIndex', nowArrIndex, 'nowFrame', nowFrame);
44
- if (!cRefArr[0].current) {
45
- //组件已经被卸载
46
- onFinish && onFinish();
103
+ // 范围或模式或数据变化时,保证重新初始化并对齐到合法帧
104
+ var target = Math.max(startIndex, Math.min(showedFrameRef.current, endIndex));
105
+ seek(target);
106
+ // 卸载时无需特殊处理:nextFrame 在组件卸载时会检测到 ref 已为 null 并停止调度
107
+ }, [startIndex, endIndex, spriteClassArr, srcArr, useSprite]);
108
+ // 尺寸变化时,同步更新已挂载节点尺寸
109
+ useEffect(function () {
110
+ cRefArr.forEach(function (ref) {
111
+ if (ref.current) {
112
+ ref.current.style.width = width;
113
+ ref.current.style.height = height;
114
+ }
115
+ });
116
+ dRefArr.forEach(function (ref) {
117
+ if (ref.current) {
118
+ ref.current.style.width = width;
119
+ ref.current.style.height = height;
120
+ }
121
+ });
122
+ }, [width, height]);
123
+ // 时长或帧数变化时,若在播放中,重设起始时间以尽量平滑过渡
124
+ useEffect(function () {
125
+ if (!isPlayingRef.current)
126
+ return;
127
+ var v = frameCount > 0 ? totalTime / frameCount : 0;
128
+ if (v > 0) {
129
+ startTimeRef.current = Date.now() - showedFrameRef.current * v;
130
+ }
131
+ }, [totalTime, frameCount]);
132
+ // 循环开关变化时,重启主循环以应用最新逻辑
133
+ useEffect(function () {
134
+ if (!isPlayingRef.current)
135
+ return;
136
+ var v = frameCount > 0 ? totalTime / frameCount : 0;
137
+ startTimeRef.current = v > 0 ? Date.now() - showedFrameRef.current * v : Date.now();
138
+ nextFrame(startTimeRef.current, showedFrameRef.current);
139
+ }, [loop]);
140
+ // ------------------------------ 帧动画主循环 ------------------------------
141
+ var nextFrame = function (startTime, showedFrame) {
142
+ // 如果这不是最新的一轮动画,直接忽略
143
+ if (startTime !== startTimeRef.current)
144
+ return;
145
+ // 暂停状态下不再推进帧,也不调度下一轮
146
+ if (!isPlayingRef.current)
147
+ return;
148
+ var v = frameCount > 0 ? totalTime / frameCount : 0;
149
+ if (v <= 0)
47
150
  return;
151
+ var nowTime = Date.now();
152
+ var rawFrame = Math.floor((nowTime - startTime) / v); // 从动画开始以来的原始帧序号
153
+ // 计算在播放区间内的实际帧序号
154
+ var nowFrame = loop ? startIndex + ((((rawFrame - startIndex) % rangeLen) + rangeLen) % rangeLen) : rawFrame;
155
+ var bufferLen = useSprite ? dRefArr.length : cRefArr.length;
156
+ var nowArrIndex = bufferLen > 0 ? nowFrame % bufferLen : 0; // 使用第几个预加载元素
157
+ var showedArrIndex = bufferLen > 0 ? showedFrame % bufferLen : 0;
158
+ // 组件已卸载
159
+ if (useSprite) {
160
+ if (!dRefArr[0].current) {
161
+ // 若根容器仍在,说明正处于模式切换的挂载窗口期,等待下一帧重试
162
+ if (rootRef.current) {
163
+ requestAnimationFrame(function () {
164
+ if (isPlayingRef.current)
165
+ nextFrame(startTime, showedFrame);
166
+ });
167
+ return;
168
+ }
169
+ onFinish && onFinish();
170
+ return;
171
+ }
172
+ }
173
+ else {
174
+ if (!cRefArr[0].current) {
175
+ if (rootRef.current) {
176
+ requestAnimationFrame(function () {
177
+ if (isPlayingRef.current)
178
+ nextFrame(startTime, showedFrame);
179
+ });
180
+ return;
181
+ }
182
+ onFinish && onFinish();
183
+ return;
184
+ }
48
185
  }
49
- if (nowFrame >= srcArr.length) {
186
+ // 动画结束(非循环情况下)
187
+ if (!loop && nowFrame > endIndex) {
50
188
  onFinish && onFinish();
51
- if (!loop) {
52
- cRefArr.forEach(function (ref, i) {
189
+ if (useSprite) {
190
+ dRefArr.forEach(function (ref) {
191
+ if (ref.current)
192
+ ref.current.style.visibility = 'hidden';
193
+ });
194
+ if (showLastFrame && dRefArr[0].current) {
195
+ dRefArr[0].current.className = spriteClassArr[endIndex];
196
+ dRefArr[0].current.style.visibility = 'visible';
197
+ }
198
+ }
199
+ else {
200
+ cRefArr.forEach(function (ref) {
53
201
  ref.current.style.visibility = 'hidden';
54
202
  });
55
203
  if (showLastFrame) {
56
- //跳帧的话,最后一帧可能不是最后一张图片,设为最后一张图片
57
- cRefArr[0].current.src = srcArr[srcArr.length - 1];
204
+ cRefArr[0].current.src = srcArr[endIndex];
58
205
  cRefArr[0].current.style.visibility = 'visible';
59
206
  }
60
- return;
61
207
  }
62
- //重置img标签的 src 和 visibility
63
- cRefArr.forEach(function (ref, i) {
64
- ref.current.style.visibility = 'hidden';
65
- ref.current.src = srcArr[i];
66
- });
67
- startTime = Date.now();
68
- cRefArr[0].current.style.visibility = 'visible';
69
- requestAnimationFrame(function () {
70
- nextFrame(startTime, v, 0);
71
- });
72
208
  return;
73
209
  }
210
+ // 帧更新
74
211
  if (nowFrame !== showedFrame) {
75
- cRefArr[nowArrIndex].current.style.visibility = 'visible';
76
- //隐藏上一帧
77
- cRefArr[showedArrIndex].current.style.visibility = 'hidden';
78
- //可能跳帧,在所有其他的元素上预加载
79
- cRefArr.forEach(function (ref, i) {
80
- if (i !== nowArrIndex) {
81
- var offset = (i + cRefArr.length - nowArrIndex) % cRefArr.length;
82
- var offsetFrame = (nowFrame + offset) % srcArr.length;
83
- if (ref.current.src !== srcArr[offsetFrame]) {
84
- ref.current.src = srcArr[offsetFrame];
212
+ if (useSprite) {
213
+ dRefArr[nowArrIndex].current.style.visibility = 'visible';
214
+ dRefArr[showedArrIndex].current.style.visibility = 'hidden';
215
+ // 可能出现跳帧,为其余 div 做预加载(通过 className 触发 background 图片加载)
216
+ dRefArr.forEach(function (ref, i) {
217
+ if (i !== nowArrIndex) {
218
+ var offset = (i + dRefArr.length - nowArrIndex) % dRefArr.length;
219
+ var offsetFrame = startIndex + ((nowFrame - startIndex + offset) % rangeLen);
220
+ if (ref.current.className !== spriteClassArr[offsetFrame]) {
221
+ ref.current.className = spriteClassArr[offsetFrame];
222
+ }
85
223
  }
86
- }
87
- });
224
+ });
225
+ }
226
+ else {
227
+ cRefArr[nowArrIndex].current.style.visibility = 'visible';
228
+ cRefArr[showedArrIndex].current.style.visibility = 'hidden';
229
+ // 可能出现跳帧,为其余 img 做预加载
230
+ cRefArr.forEach(function (ref, i) {
231
+ if (i !== nowArrIndex) {
232
+ var offset = (i + cRefArr.length - nowArrIndex) % cRefArr.length;
233
+ var offsetFrame = startIndex + ((nowFrame - startIndex + offset) % rangeLen);
234
+ if (ref.current.src !== srcArr[offsetFrame]) {
235
+ ref.current.src = srcArr[offsetFrame];
236
+ }
237
+ }
238
+ });
239
+ }
88
240
  showedFrame = nowFrame;
241
+ showedFrameRef.current = nowFrame;
89
242
  onShow && onShow(nowFrame);
243
+ var curnode = useSprite ? dRefArr[nowArrIndex].current : cRefArr[nowArrIndex].current;
244
+ console.log('curnode', curnode === null || curnode === void 0 ? void 0 : curnode.getBoundingClientRect());
245
+ }
246
+ // 下一帧(仅在播放状态下调度)
247
+ if (isPlayingRef.current) {
248
+ requestAnimationFrame(function () {
249
+ nextFrame(startTime, showedFrame);
250
+ });
90
251
  }
91
- requestAnimationFrame(function () {
92
- nextFrame(startTime, v, showedFrame);
93
- });
94
252
  };
95
- return (h("div", { style: { width: width, height: height }, id: rootId, className: rootClassName }, imgEleArr));
253
+ // ------------------------------ 组件渲染 ------------------------------
254
+ return (h("div", { ref: rootRef, style: { width: width, height: height }, id: rootId, className: rootClassName }, useSprite ? spriteEleArr : imgEleArr));
96
255
  };