rn-marquee-text 2.0.2 → 2.0.4
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/README.md +136 -194
- package/dist/MarqueeText.d.ts +116 -11
- package/dist/MarqueeText.js +206 -62
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,233 +1,175 @@
|
|
|
1
|
-
|
|
1
|
+
Here's a more professional and well-structured version of your README file with improved formatting, organization, and a "Copy All" button:
|
|
2
2
|
|
|
3
|
+
```markdown
|
|
4
|
+
# React Native Marquee Text 🚀
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
[](https://www.npmjs.com/package/rn-marquee-text)
|
|
7
|
+
[](https://github.com/yourusername/rn-marquee-text/blob/main/LICENSE)
|
|
8
|
+
[](https://www.npmjs.com/package/rn-marquee-text)
|
|
9
|
+
[](http://makeapullrequest.com)
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-

|
|
8
|
-

|
|
11
|
+
A high-performance, feature-rich marquee component for React Native with smooth animations and flexible configuration.
|
|
9
12
|
|
|
10
|
-
|
|
13
|
+

|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
15
|
+
## Table of Contents
|
|
16
|
+
- [Features](#features-)
|
|
17
|
+
- [Installation](#installation-)
|
|
18
|
+
- [Usage](#usage-)
|
|
19
|
+
- [API Reference](#api-reference-)
|
|
20
|
+
- [Examples](#examples-)
|
|
21
|
+
- [Troubleshooting](#troubleshooting-)
|
|
22
|
+
- [Contributing](#contributing-)
|
|
23
|
+
- [License](#license-)
|
|
20
24
|
|
|
21
|
-
##
|
|
25
|
+
## Features ✨
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
- **Multi-directional scrolling**: Horizontal and vertical marquee effects
|
|
28
|
+
- **Animation modes**: Loop and bounce animations
|
|
29
|
+
- **Performance optimized**: Built with React Native Reanimated 2
|
|
30
|
+
- **Customizable**: Control speed, delay, spacing, and more
|
|
31
|
+
- **Interactive**: Gesture support for pausing/resuming
|
|
32
|
+
- **Flexible content**: Supports both text and custom components
|
|
33
|
+
- **TypeScript ready**: Complete type definitions included
|
|
30
34
|
|
|
31
|
-
|
|
35
|
+
## Installation 📦
|
|
32
36
|
|
|
33
37
|
```bash
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Then update your `babel.config.js`:
|
|
38
|
+
# Using npm
|
|
39
|
+
npm install rn-marquee-text react-native-reanimated react-native-gesture-handler
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
presets: ['babel-preset-expo'],
|
|
42
|
-
plugins: ['react-native-reanimated/plugin'],
|
|
43
|
-
};
|
|
41
|
+
# Using yarn
|
|
42
|
+
yarn add rn-marquee-text react-native-reanimated react-native-gesture-handler
|
|
44
43
|
```
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
### Peer Dependencies
|
|
46
|
+
Ensure you've properly installed and configured:
|
|
47
|
+
- [React Native Reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/installation)
|
|
48
|
+
- [React Native Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/)
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
## Usage 🚀
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
import
|
|
54
|
-
|
|
55
|
-
const Example = () => (
|
|
56
|
-
<AutoScroll
|
|
57
|
-
style={{ height: 100, width: '100%' }}
|
|
58
|
-
mode={AnimationMode.LOOP}
|
|
59
|
-
speed={40}
|
|
60
|
-
>
|
|
61
|
-
This is a long text that will scroll if it overflows its container.
|
|
62
|
-
</AutoScroll>
|
|
63
|
-
);
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
#### Props
|
|
52
|
+
### Basic Implementation
|
|
53
|
+
```jsx
|
|
54
|
+
import React from 'react';
|
|
55
|
+
import { StyleSheet, View } from 'react-native';
|
|
56
|
+
import MarqueeText from 'rn-marquee-text';
|
|
67
57
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
| children | ReactNode | (required) | Content to scroll |
|
|
71
|
-
| mode | 'loop' \| 'bounce' | 'loop' | Animation type |
|
|
72
|
-
| speed | number | 30 | Speed in px/sec |
|
|
73
|
-
| delay | number | 1500 | Delay before animation starts |
|
|
74
|
-
| endPauseDuration | number | 1000 | Pause time at edges (for bounce mode) |
|
|
75
|
-
| style | ViewStyle | {} | Container styling |
|
|
76
|
-
| textStyle | TextStyle | {} | Text styling (when children is string) |
|
|
77
|
-
| enabled | boolean | true | Enable or disable animation |
|
|
78
|
-
| direction | 'horizontal' \| 'vertical' | 'vertical' | Scroll direction |
|
|
79
|
-
|
|
80
|
-
### 2️⃣ MarqueeText — For ticker-style text
|
|
81
|
-
|
|
82
|
-
#### Basic Usage
|
|
83
|
-
|
|
84
|
-
```tsx
|
|
85
|
-
import { MarqueeText } from 'rn-marquee-text';
|
|
86
|
-
import { View, Text } from 'react-native';
|
|
87
|
-
|
|
88
|
-
const Example = () => (
|
|
89
|
-
<View style={{ width: '100%', borderRadius: 8, overflow: 'hidden' }}>
|
|
58
|
+
const App = () => (
|
|
59
|
+
<View style={styles.container}>
|
|
90
60
|
<MarqueeText
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
fontSize={14}
|
|
95
|
-
direction="horizontal"
|
|
61
|
+
style={styles.marquee}
|
|
62
|
+
speed={40}
|
|
63
|
+
textStyle={styles.text}
|
|
96
64
|
>
|
|
97
|
-
|
|
98
|
-
Breaking News: This is a marquee text scrolling horizontally!
|
|
99
|
-
</Text>
|
|
65
|
+
Your scrolling text goes here
|
|
100
66
|
</MarqueeText>
|
|
101
67
|
</View>
|
|
102
68
|
);
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
#### Props
|
|
106
|
-
|
|
107
|
-
| Prop | Type | Default | Description |
|
|
108
|
-
|------|------|---------|-------------|
|
|
109
|
-
| text | string | (required) | Text to display (alternative to children) |
|
|
110
|
-
| speed | number | 80 | Speed in px/sec |
|
|
111
|
-
| backgroundColor | string | #000 | Marquee background color |
|
|
112
|
-
| textColor | string | #fff | Text color |
|
|
113
|
-
| fontSize | number | 16 | Font size |
|
|
114
|
-
| paddingVertical | number | 8 | Vertical padding |
|
|
115
|
-
| paddingHorizontal | number | 12 | Horizontal padding |
|
|
116
|
-
| delay | number | 1000 | Delay before animation starts |
|
|
117
|
-
| bounceMode | boolean | false | Whether to bounce at edges |
|
|
118
|
-
| endPauseDuration | number | 2000 | Pause at each end (only in bounce mode) |
|
|
119
|
-
|
|
120
|
-
## 💡 Advanced Examples
|
|
121
|
-
|
|
122
|
-
### 🔁 AutoScroll with Custom Content
|
|
123
|
-
|
|
124
|
-
```tsx
|
|
125
|
-
import React from 'react';
|
|
126
|
-
import { View, Text, StyleSheet } from 'react-native';
|
|
127
|
-
import AutoScroll from 'rn-marquee-text';
|
|
128
|
-
|
|
129
|
-
const CardScroller = () => (
|
|
130
|
-
<AutoScroll style={styles.scrollContainer} direction="horizontal">
|
|
131
|
-
<View style={styles.contentContainer}>
|
|
132
|
-
{[1, 2, 3, 4, 5].map((item) => (
|
|
133
|
-
<View key={item} style={styles.card}>
|
|
134
|
-
<Text style={styles.cardText}>Card {item}</Text>
|
|
135
|
-
</View>
|
|
136
|
-
))}
|
|
137
|
-
</View>
|
|
138
|
-
</AutoScroll>
|
|
139
|
-
);
|
|
140
69
|
|
|
141
70
|
const styles = StyleSheet.create({
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
},
|
|
146
|
-
contentContainer: {
|
|
147
|
-
flexDirection: 'row',
|
|
148
|
-
padding: 10,
|
|
149
|
-
},
|
|
150
|
-
card: {
|
|
151
|
-
width: 100,
|
|
152
|
-
height: 100,
|
|
153
|
-
backgroundColor: '#f0f0f0',
|
|
154
|
-
borderRadius: 8,
|
|
155
|
-
marginRight: 10,
|
|
156
|
-
justifyContent: 'center',
|
|
157
|
-
alignItems: 'center',
|
|
158
|
-
},
|
|
159
|
-
cardText: {
|
|
160
|
-
fontSize: 16,
|
|
161
|
-
fontWeight: 'bold',
|
|
162
|
-
},
|
|
71
|
+
container: { flex: 1, justifyContent: 'center', padding: 20 },
|
|
72
|
+
marquee: { height: 50, backgroundColor: '#f5f5f5', borderRadius: 8 },
|
|
73
|
+
text: { fontSize: 16, color: '#333' }
|
|
163
74
|
});
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### 📰 News Ticker with MarqueeText
|
|
167
|
-
|
|
168
|
-
```tsx
|
|
169
|
-
import React from 'react';
|
|
170
|
-
import { View, Text, StyleSheet } from 'react-native';
|
|
171
|
-
import { MarqueeText } from 'rn-marquee-text';
|
|
172
|
-
|
|
173
|
-
const NewsTicker = () => (
|
|
174
|
-
<View style={styles.tickerContainer}>
|
|
175
|
-
<MarqueeText speed={70} bounceMode={false}>
|
|
176
|
-
<Text style={styles.cardText}>
|
|
177
|
-
BREAKING NEWS: Latest updates on global events. Markets reach all-time high.
|
|
178
|
-
</Text>
|
|
179
|
-
</MarqueeText>
|
|
180
|
-
</View>
|
|
181
|
-
);
|
|
182
75
|
|
|
183
|
-
|
|
184
|
-
tickerContainer: {
|
|
185
|
-
width: '100%',
|
|
186
|
-
borderRadius: 4,
|
|
187
|
-
overflow: 'hidden',
|
|
188
|
-
marginVertical: 10,
|
|
189
|
-
},
|
|
190
|
-
cardText: {
|
|
191
|
-
fontSize: 14,
|
|
192
|
-
paddingHorizontal: 10,
|
|
193
|
-
},
|
|
194
|
-
});
|
|
76
|
+
export default App;
|
|
195
77
|
```
|
|
196
78
|
|
|
197
|
-
##
|
|
79
|
+
## API Reference 📚
|
|
198
80
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
81
|
+
### Props
|
|
82
|
+
| Prop | Type | Default | Description |
|
|
83
|
+
|------|------|---------|-------------|
|
|
84
|
+
| `children` | React.ReactNode | Required | Content to scroll (string or components) |
|
|
85
|
+
| `mode` | 'loop' \| 'bounce' | 'loop' | Animation behavior |
|
|
86
|
+
| `speed` | number | 30 | Pixels per second |
|
|
87
|
+
| `direction` | 'horizontal' \| 'vertical' | 'horizontal' | Scroll direction |
|
|
88
|
+
| `enabled` | boolean | true | Animation active state |
|
|
89
|
+
| `delay` | number | 1500 | Initial delay (ms) |
|
|
90
|
+
| `endPauseDuration` | number | 1000 | Pause at end (bounce mode) |
|
|
91
|
+
|
|
92
|
+
### Methods (via ref)
|
|
93
|
+
```jsx
|
|
94
|
+
const marqueeRef = useRef();
|
|
95
|
+
|
|
96
|
+
// Start animation
|
|
97
|
+
marqueeRef.current?.start();
|
|
98
|
+
|
|
99
|
+
// Stop animation
|
|
100
|
+
marqueeRef.current?.stop();
|
|
101
|
+
|
|
102
|
+
// Check if active
|
|
103
|
+
const isActive = marqueeRef.current?.isActive;
|
|
104
|
+
```
|
|
204
105
|
|
|
205
|
-
##
|
|
106
|
+
## Examples 🎨
|
|
107
|
+
|
|
108
|
+
### Vertical Bounce Marquee
|
|
109
|
+
```jsx
|
|
110
|
+
<MarqueeText
|
|
111
|
+
direction="vertical"
|
|
112
|
+
mode="bounce"
|
|
113
|
+
speed={20}
|
|
114
|
+
endPauseDuration={2000}
|
|
115
|
+
style={styles.verticalMarquee}
|
|
116
|
+
>
|
|
117
|
+
Line 1{'\n'}Line 2{'\n'}Line 3
|
|
118
|
+
</MarqueeText>
|
|
119
|
+
```
|
|
206
120
|
|
|
207
|
-
###
|
|
121
|
+
### Controlled Marquee with Buttons
|
|
122
|
+
```jsx
|
|
123
|
+
const [paused, setPaused] = useState(false);
|
|
208
124
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
125
|
+
<>
|
|
126
|
+
<MarqueeText
|
|
127
|
+
ref={marqueeRef}
|
|
128
|
+
enabled={!paused}
|
|
129
|
+
style={styles.marquee}
|
|
130
|
+
>
|
|
131
|
+
Controllable marquee text
|
|
132
|
+
</MarqueeText>
|
|
133
|
+
|
|
134
|
+
<Button
|
|
135
|
+
title={paused ? "Resume" : "Pause"}
|
|
136
|
+
onPress={() => setPaused(!paused)}
|
|
137
|
+
/>
|
|
138
|
+
</>
|
|
139
|
+
```
|
|
212
140
|
|
|
213
|
-
|
|
141
|
+
## Troubleshooting 🛠️
|
|
214
142
|
|
|
215
|
-
|
|
216
|
-
-
|
|
217
|
-
-
|
|
143
|
+
**Animation not working?**
|
|
144
|
+
- Verify Reanimated installation
|
|
145
|
+
- Check babel.config.js for Reanimated plugin
|
|
218
146
|
|
|
219
|
-
|
|
147
|
+
**Text not visible?**
|
|
148
|
+
- Ensure container has proper dimensions
|
|
149
|
+
- Verify text color contrasts with background
|
|
220
150
|
|
|
221
|
-
|
|
151
|
+
**Performance issues?**
|
|
152
|
+
- Reduce animation speed
|
|
153
|
+
- Simplify marquee content
|
|
154
|
+
- Use `frameRate` prop to limit FPS
|
|
222
155
|
|
|
223
|
-
## 🤝
|
|
156
|
+
## Contributing 🤝
|
|
157
|
+
Contributions are welcome! Please:
|
|
158
|
+
1. Open an issue to discuss changes
|
|
159
|
+
2. Ensure tests are updated
|
|
160
|
+
3. Maintain consistent code style
|
|
224
161
|
|
|
225
|
-
|
|
162
|
+
## License 📄
|
|
163
|
+
MIT © [Your Name](https://github.com/yourusername)
|
|
226
164
|
|
|
227
|
-
|
|
165
|
+
<button onclick="copyToClipboard()">Copy All README Content</button>
|
|
228
166
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
167
|
+
<script>
|
|
168
|
+
function copyToClipboard() {
|
|
169
|
+
const content = document.querySelector('article').innerText;
|
|
170
|
+
navigator.clipboard.writeText(content)
|
|
171
|
+
.then(() => alert('README copied to clipboard!'))
|
|
172
|
+
.catch(err => console.error('Failed to copy:', err));
|
|
173
|
+
}
|
|
174
|
+
</script>
|
|
175
|
+
```
|
package/dist/MarqueeText.d.ts
CHANGED
|
@@ -1,14 +1,119 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ViewStyle, TextStyle } from 'react-native';
|
|
3
|
+
/**
|
|
4
|
+
* Animation modes for the marquee text
|
|
5
|
+
*/
|
|
6
|
+
declare enum AnimationMode {
|
|
7
|
+
LOOP = "loop",
|
|
8
|
+
BOUNCE = "bounce"
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Direction of the marquee animation
|
|
12
|
+
*/
|
|
13
|
+
type MarqueeDirection = 'horizontal' | 'vertical';
|
|
14
|
+
/**
|
|
15
|
+
* Props for the MarqueeText component
|
|
16
|
+
*/
|
|
17
|
+
interface MarqueeTextProps {
|
|
18
|
+
/**
|
|
19
|
+
* Content to be scrolled (string or React nodes)
|
|
20
|
+
*/
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
/**
|
|
23
|
+
* The animation mode: 'loop' for continuous scrolling or 'bounce' for back-and-forth
|
|
24
|
+
* @default AnimationMode.LOOP
|
|
25
|
+
*/
|
|
26
|
+
mode?: AnimationMode;
|
|
27
|
+
/**
|
|
28
|
+
* Speed of the scrolling animation in pixels per second
|
|
29
|
+
* @default 30
|
|
30
|
+
*/
|
|
4
31
|
speed?: number;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
paddingHorizontal?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Delay in milliseconds before starting the animation
|
|
34
|
+
* @default 1500
|
|
35
|
+
*/
|
|
10
36
|
delay?: number;
|
|
11
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Duration in milliseconds to pause at the end of the content (only for bounce mode)
|
|
39
|
+
* @default 1000
|
|
40
|
+
*/
|
|
12
41
|
endPauseDuration?: number;
|
|
13
|
-
|
|
14
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Container style
|
|
44
|
+
*/
|
|
45
|
+
style?: ViewStyle;
|
|
46
|
+
/**
|
|
47
|
+
* Text style (when children is a string)
|
|
48
|
+
*/
|
|
49
|
+
textStyle?: TextStyle;
|
|
50
|
+
/**
|
|
51
|
+
* Whether to enable the animation
|
|
52
|
+
* @default true
|
|
53
|
+
*/
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Direction of scrolling ('horizontal' or 'vertical')
|
|
57
|
+
* @default 'horizontal'
|
|
58
|
+
*/
|
|
59
|
+
direction?: MarqueeDirection;
|
|
60
|
+
/**
|
|
61
|
+
* Spacing between cloned elements
|
|
62
|
+
* @default 20
|
|
63
|
+
*/
|
|
64
|
+
spacing?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Allow user interaction with gesture handling
|
|
67
|
+
* @default true
|
|
68
|
+
*/
|
|
69
|
+
withGesture?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Custom frame rate for animation
|
|
72
|
+
*/
|
|
73
|
+
frameRate?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Reverse the animation direction
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
reverse?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Background color for the container
|
|
81
|
+
* @default 'transparent'
|
|
82
|
+
*/
|
|
83
|
+
backgroundColor?: string;
|
|
84
|
+
/**
|
|
85
|
+
* Callback when animation starts
|
|
86
|
+
*/
|
|
87
|
+
onAnimationStart?: () => void;
|
|
88
|
+
/**
|
|
89
|
+
* Callback when animation stops
|
|
90
|
+
*/
|
|
91
|
+
onAnimationStop?: () => void;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Reference handle for the MarqueeText component
|
|
95
|
+
*/
|
|
96
|
+
export interface MarqueeTextRef {
|
|
97
|
+
/**
|
|
98
|
+
* Start the marquee animation
|
|
99
|
+
*/
|
|
100
|
+
start: () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Stop the marquee animation
|
|
103
|
+
*/
|
|
104
|
+
stop: () => void;
|
|
105
|
+
/**
|
|
106
|
+
* Whether the animation is currently active
|
|
107
|
+
*/
|
|
108
|
+
isActive: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* MarqueeText component for scrolling text or content
|
|
112
|
+
*
|
|
113
|
+
* This component creates a scrolling effect for text or other content.
|
|
114
|
+
* It supports both horizontal and vertical scrolling, different animation
|
|
115
|
+
* modes, and user interaction via gestures.
|
|
116
|
+
*/
|
|
117
|
+
declare const MarqueeText: React.ForwardRefExoticComponent<MarqueeTextProps & React.RefAttributes<MarqueeTextRef>>;
|
|
118
|
+
export { MarqueeText, AnimationMode };
|
|
119
|
+
export type { MarqueeDirection, MarqueeTextProps };
|
package/dist/MarqueeText.js
CHANGED
|
@@ -1,64 +1,208 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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
|
+
import * as React from 'react';
|
|
2
|
+
import { StyleSheet, View, Text } from 'react-native';
|
|
3
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
4
|
+
import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue, useFrameCallback, withDecay, } from 'react-native-reanimated';
|
|
5
|
+
import { useFocusEffect } from '@react-navigation/native';
|
|
6
|
+
/**
|
|
7
|
+
* Animation modes for the marquee text
|
|
8
|
+
*/
|
|
9
|
+
var AnimationMode;
|
|
10
|
+
(function (AnimationMode) {
|
|
11
|
+
AnimationMode["LOOP"] = "loop";
|
|
12
|
+
AnimationMode["BOUNCE"] = "bounce";
|
|
13
|
+
})(AnimationMode || (AnimationMode = {}));
|
|
14
|
+
/**
|
|
15
|
+
* Component to render a child item in the marquee
|
|
16
|
+
*/
|
|
17
|
+
var AnimatedChild = React.memo(function (_a) {
|
|
18
|
+
var index = _a.index, children = _a.children, anim = _a.anim, contentMeasurement = _a.contentMeasurement, spacing = _a.spacing, direction = _a.direction;
|
|
19
|
+
var style = useAnimatedStyle(function () {
|
|
20
|
+
var _a;
|
|
21
|
+
var isVertical = direction === 'vertical';
|
|
22
|
+
var dimension = isVertical ? contentMeasurement.value.height : contentMeasurement.value.width;
|
|
23
|
+
if (dimension <= 0)
|
|
24
|
+
return {};
|
|
25
|
+
var position = (index - 1) * (dimension + spacing);
|
|
26
|
+
var translation = -(anim.value % (dimension + spacing));
|
|
27
|
+
return _a = {
|
|
28
|
+
position: 'absolute'
|
|
29
|
+
},
|
|
30
|
+
_a[isVertical ? 'top' : 'left'] = position,
|
|
31
|
+
_a.transform = isVertical
|
|
32
|
+
? [{ translateY: translation }]
|
|
33
|
+
: [{ translateX: translation }],
|
|
34
|
+
_a;
|
|
35
|
+
}, [index, spacing, contentMeasurement, direction]);
|
|
36
|
+
return <Animated.View style={style}>{children}</Animated.View>;
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* MarqueeText component for scrolling text or content
|
|
40
|
+
*
|
|
41
|
+
* This component creates a scrolling effect for text or other content.
|
|
42
|
+
* It supports both horizontal and vertical scrolling, different animation
|
|
43
|
+
* modes, and user interaction via gestures.
|
|
44
|
+
*/
|
|
45
|
+
var MarqueeText = React.forwardRef(function (_a, ref) {
|
|
46
|
+
var children = _a.children, _b = _a.mode, mode = _b === void 0 ? AnimationMode.LOOP : _b, _c = _a.speed, speed = _c === void 0 ? 30 : _c, _d = _a.delay, delay = _d === void 0 ? 1500 : _d, _e = _a.endPauseDuration, endPauseDuration = _e === void 0 ? 1000 : _e, style = _a.style, textStyle = _a.textStyle, _f = _a.enabled, enabled = _f === void 0 ? true : _f, _g = _a.direction, direction = _g === void 0 ? 'horizontal' : _g, _h = _a.spacing, spacing = _h === void 0 ? 20 : _h, _j = _a.withGesture, withGesture = _j === void 0 ? true : _j, frameRate = _a.frameRate, _k = _a.reverse, reverse = _k === void 0 ? false : _k, _l = _a.backgroundColor, backgroundColor = _l === void 0 ? 'transparent' : _l, onAnimationStart = _a.onAnimationStart, onAnimationStop = _a.onAnimationStop;
|
|
47
|
+
var isVertical = direction === 'vertical';
|
|
48
|
+
var isBounceMode = mode === AnimationMode.BOUNCE;
|
|
49
|
+
var containerMeasurement = useSharedValue({
|
|
50
|
+
width: 0,
|
|
51
|
+
height: 0,
|
|
52
|
+
x: 0,
|
|
53
|
+
y: 0,
|
|
54
|
+
});
|
|
55
|
+
var contentMeasurement = useSharedValue({
|
|
56
|
+
width: 0,
|
|
57
|
+
height: 0,
|
|
58
|
+
x: 0,
|
|
59
|
+
y: 0,
|
|
46
60
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
61
|
+
var _m = React.useState(0), cloneTimes = _m[0], setCloneTimes = _m[1];
|
|
62
|
+
var anim = useSharedValue(0);
|
|
63
|
+
var isMounted = React.useRef(true);
|
|
64
|
+
var isActive = useSharedValue(false);
|
|
65
|
+
var frameRateMs = frameRate ? 1000 / frameRate : 1000 / 60; // Default to 60fps
|
|
66
|
+
var pixelsPerMs = speed / 1000;
|
|
67
|
+
// Animation frame callback
|
|
68
|
+
var frameCallback = useFrameCallback(function (frameInfo) {
|
|
69
|
+
if (!enabled || frameInfo.timeSincePreviousFrame === null)
|
|
70
|
+
return;
|
|
71
|
+
var deltaTime = frameRateMs
|
|
72
|
+
? frameInfo.timeSincePreviousFrame / frameRateMs
|
|
73
|
+
: frameInfo.timeSincePreviousFrame;
|
|
74
|
+
anim.value += (reverse ? -1 : 1) * pixelsPerMs * deltaTime;
|
|
75
|
+
}, false);
|
|
76
|
+
// Calculate how many clones we need to fill the screen
|
|
77
|
+
useAnimatedReaction(function () {
|
|
78
|
+
var contentDim = isVertical ? contentMeasurement.value.height : contentMeasurement.value.width;
|
|
79
|
+
var containerDim = isVertical ? containerMeasurement.value.height : containerMeasurement.value.width;
|
|
80
|
+
if (contentDim <= 0 || containerDim <= 0)
|
|
81
|
+
return 0;
|
|
82
|
+
// Need enough clones to fill the container plus buffer for continuous scrolling
|
|
83
|
+
return Math.ceil(containerDim / contentDim) + 2;
|
|
84
|
+
}, function (times) {
|
|
85
|
+
if (times > 0 && isMounted.current) {
|
|
86
|
+
runOnJS(setCloneTimes)(times);
|
|
87
|
+
}
|
|
88
|
+
}, [direction]);
|
|
89
|
+
// Control functions
|
|
90
|
+
var start = React.useCallback(function () {
|
|
91
|
+
if (!enabled)
|
|
92
|
+
return;
|
|
93
|
+
frameCallback.setActive(true);
|
|
94
|
+
isActive.value = true;
|
|
95
|
+
onAnimationStart === null || onAnimationStart === void 0 ? void 0 : onAnimationStart();
|
|
96
|
+
}, [frameCallback, enabled, onAnimationStart]);
|
|
97
|
+
var stop = React.useCallback(function () {
|
|
98
|
+
frameCallback.setActive(false);
|
|
99
|
+
isActive.value = false;
|
|
100
|
+
onAnimationStop === null || onAnimationStop === void 0 ? void 0 : onAnimationStop();
|
|
101
|
+
}, [frameCallback, onAnimationStop]);
|
|
102
|
+
// Expose controls via ref
|
|
103
|
+
React.useImperativeHandle(ref, function () { return ({
|
|
104
|
+
start: start,
|
|
105
|
+
stop: stop,
|
|
106
|
+
get isActive() { return isActive.value; }
|
|
107
|
+
}); });
|
|
108
|
+
// Setup gesture handling
|
|
109
|
+
var pan = React.useMemo(function () {
|
|
110
|
+
return Gesture.Pan()
|
|
111
|
+
.enabled(withGesture && enabled)
|
|
112
|
+
.onBegin(function () {
|
|
113
|
+
// Stop auto-animation when user interacts
|
|
114
|
+
runOnJS(stop)();
|
|
115
|
+
})
|
|
116
|
+
.onChange(function (e) {
|
|
117
|
+
// Move according to user's gesture
|
|
118
|
+
anim.value += -(isVertical ? e.changeY : e.changeX);
|
|
119
|
+
})
|
|
120
|
+
.onFinalize(function (e) {
|
|
121
|
+
// Apply momentum scrolling when user releases
|
|
122
|
+
anim.value = withDecay({
|
|
123
|
+
velocity: -(isVertical ? e.velocityY : e.velocityX),
|
|
124
|
+
}, function (finished) {
|
|
125
|
+
// Restart auto-animation when decay finishes
|
|
126
|
+
if (finished)
|
|
127
|
+
runOnJS(start)();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
}, [withGesture, isVertical, anim, start, stop, enabled]);
|
|
131
|
+
// Handle focus/unmount
|
|
132
|
+
useFocusEffect(React.useCallback(function () {
|
|
133
|
+
// Start animation when screen comes into focus
|
|
134
|
+
if (enabled) {
|
|
135
|
+
start();
|
|
136
|
+
}
|
|
137
|
+
return function () {
|
|
138
|
+
// Stop animation when screen loses focus
|
|
139
|
+
stop();
|
|
140
|
+
anim.value = 0;
|
|
141
|
+
};
|
|
142
|
+
}, [start, stop, anim, enabled]));
|
|
143
|
+
// Cleanup on unmount
|
|
144
|
+
React.useEffect(function () {
|
|
145
|
+
return function () {
|
|
146
|
+
isMounted.current = false;
|
|
147
|
+
};
|
|
148
|
+
}, []);
|
|
149
|
+
// Start/stop based on enabled prop changes
|
|
150
|
+
React.useEffect(function () {
|
|
151
|
+
if (enabled) {
|
|
152
|
+
start();
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
stop();
|
|
156
|
+
}
|
|
157
|
+
}, [enabled, start, stop]);
|
|
158
|
+
// Render the content
|
|
159
|
+
var renderContent = function () {
|
|
160
|
+
if (typeof children === 'string') {
|
|
161
|
+
return <Text style={textStyle}>{children}</Text>;
|
|
162
|
+
}
|
|
163
|
+
return children;
|
|
164
|
+
};
|
|
165
|
+
return (<Animated.View style={[
|
|
166
|
+
styles.container,
|
|
167
|
+
{ backgroundColor: backgroundColor },
|
|
168
|
+
style
|
|
169
|
+
]} onLayout={function (ev) {
|
|
170
|
+
containerMeasurement.value = ev.nativeEvent.layout;
|
|
171
|
+
}} pointerEvents="box-none">
|
|
172
|
+
<GestureDetector gesture={pan}>
|
|
173
|
+
<Animated.View style={isVertical ? styles.column : styles.row} pointerEvents="box-none">
|
|
174
|
+
{/* Hidden element for measuring original content size */}
|
|
175
|
+
<View style={styles.hidden} onLayout={function (ev) {
|
|
176
|
+
contentMeasurement.value = ev.nativeEvent.layout;
|
|
52
177
|
}}>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
178
|
+
{renderContent()}
|
|
179
|
+
</View>
|
|
180
|
+
|
|
181
|
+
{/* Visible cloned elements */}
|
|
182
|
+
{cloneTimes > 0 &&
|
|
183
|
+
Array.from({ length: cloneTimes }).map(function (_, index) { return (<AnimatedChild key={"clone-".concat(index)} index={index} anim={anim} contentMeasurement={contentMeasurement} spacing={spacing} direction={direction}>
|
|
184
|
+
{renderContent()}
|
|
185
|
+
</AnimatedChild>); })}
|
|
186
|
+
</Animated.View>
|
|
187
|
+
</GestureDetector>
|
|
188
|
+
</Animated.View>);
|
|
189
|
+
});
|
|
190
|
+
var styles = StyleSheet.create({
|
|
191
|
+
container: {
|
|
192
|
+
overflow: 'hidden',
|
|
193
|
+
},
|
|
194
|
+
hidden: {
|
|
195
|
+
opacity: 0,
|
|
196
|
+
position: 'absolute',
|
|
197
|
+
zIndex: -1,
|
|
198
|
+
},
|
|
199
|
+
row: {
|
|
200
|
+
flexDirection: 'row',
|
|
201
|
+
position: 'relative',
|
|
202
|
+
},
|
|
203
|
+
column: {
|
|
204
|
+
flexDirection: 'column',
|
|
205
|
+
position: 'relative',
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
export { MarqueeText, AnimationMode };
|
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.4",
|
|
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
|
}
|