rn-marquee-text 2.0.2 → 2.0.3
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/dist/MarqueeText.d.ts +45 -13
- package/dist/MarqueeText.js +274 -62
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +2 -1
package/dist/MarqueeText.d.ts
CHANGED
|
@@ -1,14 +1,46 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { ViewStyle } from 'react-native';
|
|
3
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
4
|
+
type MarqueeDirection = 'horizontal' | 'vertical';
|
|
5
|
+
export type MarqueeProps = React.PropsWithChildren<{
|
|
4
6
|
speed?: number;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
spacing?: number;
|
|
8
|
+
style?: ViewStyle;
|
|
9
|
+
reverse?: boolean;
|
|
10
|
+
frameRate?: number;
|
|
11
|
+
direction?: MarqueeDirection;
|
|
12
|
+
position?: SharedValue<number>;
|
|
13
|
+
withGesture?: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
export type MarqueeRef = {
|
|
16
|
+
start: () => void;
|
|
17
|
+
stop: () => void;
|
|
18
|
+
isActive: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Marquee component that animates its children in a continuous loop.
|
|
22
|
+
*
|
|
23
|
+
* @param speed - Animation speed (pixels per frame)
|
|
24
|
+
* @param spacing - Spacing between cloned elements
|
|
25
|
+
* @param style - Container style
|
|
26
|
+
* @param reverse - Reverse animation direction
|
|
27
|
+
* @param frameRate - Custom frame rate (optional)
|
|
28
|
+
* @param direction - Animation direction ('horizontal' or 'vertical')
|
|
29
|
+
* @param position - Optional shared value to track position
|
|
30
|
+
* @param withGesture - Enable/disable gesture interactions
|
|
31
|
+
* @param ref - Ref to control animation
|
|
32
|
+
* @param children - Child elements to animate
|
|
33
|
+
*/
|
|
34
|
+
export declare const Marquee: React.NamedExoticComponent<{
|
|
35
|
+
speed?: number;
|
|
36
|
+
spacing?: number;
|
|
37
|
+
style?: ViewStyle;
|
|
38
|
+
reverse?: boolean;
|
|
39
|
+
frameRate?: number;
|
|
40
|
+
direction?: MarqueeDirection;
|
|
41
|
+
position?: SharedValue<number>;
|
|
42
|
+
withGesture?: boolean;
|
|
43
|
+
} & {
|
|
44
|
+
children?: React.ReactNode | undefined;
|
|
45
|
+
} & React.RefAttributes<MarqueeRef>>;
|
|
46
|
+
export {};
|
package/dist/MarqueeText.js
CHANGED
|
@@ -1,64 +1,276 @@
|
|
|
1
|
-
// MarqueeText.js - Complete rewrite with guaranteed scrolling
|
|
2
|
-
import React, { useEffect } from 'react';
|
|
3
|
-
import { View } from 'react-native';
|
|
4
|
-
import Animated, {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
// // MarqueeText.js - Complete rewrite with guaranteed scrolling
|
|
2
|
+
// import React, { useEffect } from 'react';
|
|
3
|
+
// import { Text, View } from 'react-native';
|
|
4
|
+
// import Animated, {
|
|
5
|
+
// useSharedValue,
|
|
6
|
+
// useAnimatedStyle,
|
|
7
|
+
// withRepeat,
|
|
8
|
+
// withTiming,
|
|
9
|
+
// withDelay,
|
|
10
|
+
// Easing,
|
|
11
|
+
// withSequence,
|
|
12
|
+
// } from 'react-native-reanimated';
|
|
13
|
+
// const MarqueeText = ({
|
|
14
|
+
// text,
|
|
15
|
+
// speed = 60,
|
|
16
|
+
// backgroundColor = '#000',
|
|
17
|
+
// textColor = '#fff',
|
|
18
|
+
// fontSize = 16,
|
|
19
|
+
// paddingVertical = 8,
|
|
20
|
+
// paddingHorizontal = 12,
|
|
21
|
+
// delay = 1000,
|
|
22
|
+
// bounceMode = false,
|
|
23
|
+
// endPauseDuration = 2000,
|
|
24
|
+
// }:{
|
|
25
|
+
// text: string;
|
|
26
|
+
// speed?: number;
|
|
27
|
+
// backgroundColor?: string;
|
|
28
|
+
// textColor?: string;
|
|
29
|
+
// fontSize?: number;
|
|
30
|
+
// paddingVertical?: number;
|
|
31
|
+
// paddingHorizontal?: number;
|
|
32
|
+
// delay?: number;
|
|
33
|
+
// bounceMode?: boolean;
|
|
34
|
+
// endPauseDuration?: number;
|
|
35
|
+
// }) => {
|
|
36
|
+
// // Force a larger content width to ensure scrolling always happens
|
|
37
|
+
// // In real usage, we would measure dynamically, but this ensures it works
|
|
38
|
+
// const contentWidth = text.length * (fontSize * 0.6);
|
|
39
|
+
// const scrollX = useSharedValue(0);
|
|
40
|
+
// useEffect(() => {
|
|
41
|
+
// // Give layout time to complete
|
|
42
|
+
// const timeoutId = setTimeout(() => {
|
|
43
|
+
// if (bounceMode) {
|
|
44
|
+
// // Bounce animation (back and forth with pauses)
|
|
45
|
+
// scrollX.value = withDelay(
|
|
46
|
+
// delay,
|
|
47
|
+
// withRepeat(
|
|
48
|
+
// withSequence(
|
|
49
|
+
// withTiming(-contentWidth, {
|
|
50
|
+
// duration: contentWidth * (1000 / speed),
|
|
51
|
+
// easing: Easing.linear
|
|
52
|
+
// }),
|
|
53
|
+
// withTiming(-contentWidth, {
|
|
54
|
+
// duration: endPauseDuration,
|
|
55
|
+
// easing: Easing.linear
|
|
56
|
+
// }),
|
|
57
|
+
// withTiming(0, {
|
|
58
|
+
// duration: contentWidth * (1000 / speed),
|
|
59
|
+
// easing: Easing.linear
|
|
60
|
+
// }),
|
|
61
|
+
// withTiming(0, {
|
|
62
|
+
// duration: endPauseDuration,
|
|
63
|
+
// easing: Easing.linear
|
|
64
|
+
// })
|
|
65
|
+
// ),
|
|
66
|
+
// -1, // Infinite repeats
|
|
67
|
+
// false
|
|
68
|
+
// )
|
|
69
|
+
// );
|
|
70
|
+
// } else {
|
|
71
|
+
// // Standard loop animation
|
|
72
|
+
// scrollX.value = withDelay(
|
|
73
|
+
// delay,
|
|
74
|
+
// withRepeat(
|
|
75
|
+
// withTiming(-contentWidth, {
|
|
76
|
+
// duration: contentWidth * (1000 / speed),
|
|
77
|
+
// easing: Easing.linear
|
|
78
|
+
// }),
|
|
79
|
+
// -1, // Infinite repeats
|
|
80
|
+
// false
|
|
81
|
+
// )
|
|
82
|
+
// );
|
|
83
|
+
// }
|
|
84
|
+
// }, 500);
|
|
85
|
+
// return () => clearTimeout(timeoutId);
|
|
86
|
+
// }, [speed, delay, contentWidth, bounceMode, endPauseDuration]);
|
|
87
|
+
// const animatedStyle = useAnimatedStyle(() => {
|
|
88
|
+
// return {
|
|
89
|
+
// transform: [{ translateX: scrollX.value }],
|
|
90
|
+
// };
|
|
91
|
+
// });
|
|
92
|
+
// return (
|
|
93
|
+
// <View
|
|
94
|
+
// style={{
|
|
95
|
+
// backgroundColor,
|
|
96
|
+
// paddingVertical,
|
|
97
|
+
// paddingHorizontal,
|
|
98
|
+
// overflow: 'hidden',
|
|
99
|
+
// }}
|
|
100
|
+
// >
|
|
101
|
+
// <Animated.Text
|
|
102
|
+
// style={[
|
|
103
|
+
// {
|
|
104
|
+
// color: textColor,
|
|
105
|
+
// fontSize,
|
|
106
|
+
// },
|
|
107
|
+
// animatedStyle,
|
|
108
|
+
// ]}
|
|
109
|
+
// numberOfLines={1}
|
|
110
|
+
// // ellipsizeMode="tail"
|
|
111
|
+
// >
|
|
112
|
+
// {text}
|
|
113
|
+
// </Animated.Text>
|
|
114
|
+
// </View>
|
|
115
|
+
// );
|
|
116
|
+
// };
|
|
117
|
+
// export default MarqueeText;
|
|
118
|
+
import * as React from 'react';
|
|
119
|
+
import { StyleSheet, View } from 'react-native';
|
|
120
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
121
|
+
import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useFrameCallback, useSharedValue, withDecay, } from 'react-native-reanimated';
|
|
122
|
+
import { useFocusEffect } from '@react-navigation/native';
|
|
123
|
+
var AnimatedChild = React.memo(function (_a) {
|
|
124
|
+
var index = _a.index, children = _a.children, anim = _a.anim, textMeasurement = _a.textMeasurement, spacing = _a.spacing, direction = _a.direction;
|
|
125
|
+
var stylez = useAnimatedStyle(function () {
|
|
126
|
+
var _a;
|
|
127
|
+
var isVertical = direction === 'vertical';
|
|
128
|
+
var dimension = isVertical ? textMeasurement.value.height : textMeasurement.value.width;
|
|
129
|
+
if (dimension <= 0)
|
|
130
|
+
return {};
|
|
131
|
+
var position = (index - 1) * (dimension + spacing);
|
|
132
|
+
var translation = -(anim.value % (dimension + spacing));
|
|
133
|
+
return _a = {
|
|
134
|
+
position: 'absolute'
|
|
135
|
+
},
|
|
136
|
+
_a[isVertical ? 'top' : 'left'] = position,
|
|
137
|
+
_a.transform = [
|
|
138
|
+
{
|
|
139
|
+
translateX: translation,
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
_a;
|
|
143
|
+
}, [index, spacing, textMeasurement, direction]);
|
|
144
|
+
return <Animated.View style={stylez}>{children}</Animated.View>;
|
|
145
|
+
});
|
|
146
|
+
/**
|
|
147
|
+
* Marquee component that animates its children in a continuous loop.
|
|
148
|
+
*
|
|
149
|
+
* @param speed - Animation speed (pixels per frame)
|
|
150
|
+
* @param spacing - Spacing between cloned elements
|
|
151
|
+
* @param style - Container style
|
|
152
|
+
* @param reverse - Reverse animation direction
|
|
153
|
+
* @param frameRate - Custom frame rate (optional)
|
|
154
|
+
* @param direction - Animation direction ('horizontal' or 'vertical')
|
|
155
|
+
* @param position - Optional shared value to track position
|
|
156
|
+
* @param withGesture - Enable/disable gesture interactions
|
|
157
|
+
* @param ref - Ref to control animation
|
|
158
|
+
* @param children - Child elements to animate
|
|
159
|
+
*/
|
|
160
|
+
export var Marquee = React.memo(React.forwardRef(function (_a, ref) {
|
|
161
|
+
var _b = _a.speed, speed = _b === void 0 ? 1 : _b, children = _a.children, _c = _a.spacing, spacing = _c === void 0 ? 0 : _c, style = _a.style, _d = _a.reverse, reverse = _d === void 0 ? false : _d, frameRate = _a.frameRate, _e = _a.direction, direction = _e === void 0 ? 'horizontal' : _e, position = _a.position, _f = _a.withGesture, withGesture = _f === void 0 ? true : _f;
|
|
162
|
+
var isVertical = direction === 'vertical';
|
|
163
|
+
var parentMeasurement = useSharedValue({
|
|
164
|
+
width: 0,
|
|
165
|
+
height: 0,
|
|
166
|
+
x: 0,
|
|
167
|
+
y: 0,
|
|
168
|
+
});
|
|
169
|
+
var textMeasurement = useSharedValue({
|
|
170
|
+
width: 0,
|
|
171
|
+
height: 0,
|
|
172
|
+
x: 0,
|
|
173
|
+
y: 0,
|
|
174
|
+
});
|
|
175
|
+
var _g = React.useState(0), cloneTimes = _g[0], setCloneTimes = _g[1];
|
|
176
|
+
var anim = useSharedValue(0);
|
|
177
|
+
var isMounted = React.useRef(true);
|
|
178
|
+
var frameRateMs = frameRate ? 1000 / frameRate : null;
|
|
179
|
+
var frameCallback = useFrameCallback(function (frameInfo) {
|
|
180
|
+
if (frameInfo.timeSincePreviousFrame === null)
|
|
181
|
+
return;
|
|
182
|
+
var frameDelta = frameRateMs
|
|
183
|
+
? frameInfo.timeSincePreviousFrame / frameRateMs
|
|
184
|
+
: 1;
|
|
185
|
+
anim.value += (reverse ? -1 : 1) * speed * frameDelta;
|
|
186
|
+
}, false);
|
|
187
|
+
useDerivedValue(function () {
|
|
188
|
+
if (position) {
|
|
189
|
+
position.value = anim.value;
|
|
190
|
+
}
|
|
46
191
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
192
|
+
useAnimatedReaction(function () {
|
|
193
|
+
var textDim = isVertical ? textMeasurement.value.height : textMeasurement.value.width;
|
|
194
|
+
var parentDim = isVertical ? parentMeasurement.value.height : parentMeasurement.value.width;
|
|
195
|
+
if (textDim <= 0 || parentDim <= 0)
|
|
196
|
+
return 0;
|
|
197
|
+
return Math.ceil(parentDim / textDim) + 2;
|
|
198
|
+
}, function (times) {
|
|
199
|
+
if (times > 0 && isMounted.current) {
|
|
200
|
+
runOnJS(setCloneTimes)(times);
|
|
201
|
+
}
|
|
202
|
+
}, [direction]);
|
|
203
|
+
// Control functions
|
|
204
|
+
var start = React.useCallback(function () {
|
|
205
|
+
frameCallback.setActive(true);
|
|
206
|
+
}, [frameCallback]);
|
|
207
|
+
var stop = React.useCallback(function () {
|
|
208
|
+
frameCallback.setActive(false);
|
|
209
|
+
}, [frameCallback]);
|
|
210
|
+
React.useImperativeHandle(ref, function () { return ({
|
|
211
|
+
start: start,
|
|
212
|
+
stop: stop,
|
|
213
|
+
isActive: frameCallback.isActive,
|
|
214
|
+
}); });
|
|
215
|
+
var pan = React.useMemo(function () {
|
|
216
|
+
return Gesture.Pan()
|
|
217
|
+
.enabled(withGesture)
|
|
218
|
+
.onBegin(function () { return runOnJS(stop)(); })
|
|
219
|
+
.onChange(function (e) {
|
|
220
|
+
anim.value += -(isVertical ? e.changeY : e.changeX);
|
|
221
|
+
})
|
|
222
|
+
.onFinalize(function (e) {
|
|
223
|
+
anim.value = withDecay({
|
|
224
|
+
velocity: -(isVertical ? e.velocityY : e.velocityX),
|
|
225
|
+
}, function (finished) { return finished && runOnJS(start)(); });
|
|
226
|
+
});
|
|
227
|
+
}, [withGesture, isVertical, anim, start, stop]);
|
|
228
|
+
// Handle focus/unmount
|
|
229
|
+
useFocusEffect(React.useCallback(function () {
|
|
230
|
+
start();
|
|
231
|
+
return function () {
|
|
232
|
+
stop();
|
|
233
|
+
anim.value = 0;
|
|
234
|
+
};
|
|
235
|
+
}, [start, stop, anim]));
|
|
236
|
+
React.useEffect(function () {
|
|
237
|
+
return function () {
|
|
238
|
+
isMounted.current = false;
|
|
239
|
+
};
|
|
240
|
+
}, []);
|
|
241
|
+
return (<Animated.View style={style} onLayout={function (ev) {
|
|
242
|
+
parentMeasurement.value = ev.nativeEvent.layout;
|
|
243
|
+
}} pointerEvents="box-none">
|
|
244
|
+
<GestureDetector gesture={pan}>
|
|
245
|
+
<Animated.View style={isVertical ? styles.column : styles.row} pointerEvents="box-none">
|
|
246
|
+
<Animated.ScrollView horizontal={!isVertical} style={styles.hidden} pointerEvents="none">
|
|
247
|
+
<View onLayout={function (ev) {
|
|
248
|
+
textMeasurement.value = ev.nativeEvent.layout;
|
|
52
249
|
}}>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
250
|
+
{children}
|
|
251
|
+
</View>
|
|
252
|
+
</Animated.ScrollView>
|
|
253
|
+
|
|
254
|
+
{cloneTimes > 0 &&
|
|
255
|
+
Array.from({ length: cloneTimes }).map(function (_, index) { return (<AnimatedChild key={"clone-".concat(index)} index={index} anim={anim} textMeasurement={textMeasurement} spacing={spacing} direction={direction}>
|
|
256
|
+
{children}
|
|
257
|
+
</AnimatedChild>); })}
|
|
258
|
+
</Animated.View>
|
|
259
|
+
</GestureDetector>
|
|
260
|
+
</Animated.View>);
|
|
261
|
+
}));
|
|
262
|
+
var styles = StyleSheet.create({
|
|
263
|
+
hidden: {
|
|
264
|
+
opacity: 0,
|
|
265
|
+
zIndex: -9999,
|
|
266
|
+
flex: 0, // Prevent ScrollView from expanding
|
|
267
|
+
},
|
|
268
|
+
row: {
|
|
269
|
+
flexDirection: 'row',
|
|
270
|
+
flex: 1,
|
|
271
|
+
},
|
|
272
|
+
column: {
|
|
273
|
+
flexDirection: 'column',
|
|
274
|
+
flex: 1,
|
|
275
|
+
},
|
|
276
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rn-marquee-text",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "A customizable marquee (scrolling) text component for React Native",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"typescript": "^5.0.0"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
+
"@react-navigation/native": "^7.1.9",
|
|
53
54
|
"react-native-gesture-handler": "^2.25.0",
|
|
54
55
|
"rn-marquee-text": "^1.0.4"
|
|
55
56
|
}
|