rn-marquee-text 2.0.3 → 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 +107 -34
- package/dist/MarqueeText.js +123 -191
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/package.json +1 -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,46 +1,119 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import
|
|
3
|
-
|
|
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
|
+
*/
|
|
4
13
|
type MarqueeDirection = 'horizontal' | 'vertical';
|
|
5
|
-
|
|
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
|
+
*/
|
|
6
31
|
speed?: number;
|
|
7
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Delay in milliseconds before starting the animation
|
|
34
|
+
* @default 1500
|
|
35
|
+
*/
|
|
36
|
+
delay?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Duration in milliseconds to pause at the end of the content (only for bounce mode)
|
|
39
|
+
* @default 1000
|
|
40
|
+
*/
|
|
41
|
+
endPauseDuration?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Container style
|
|
44
|
+
*/
|
|
8
45
|
style?: ViewStyle;
|
|
9
|
-
|
|
10
|
-
|
|
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
|
+
*/
|
|
11
59
|
direction?: MarqueeDirection;
|
|
12
|
-
|
|
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
|
+
*/
|
|
13
69
|
withGesture?: boolean;
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+
*/
|
|
16
100
|
start: () => void;
|
|
101
|
+
/**
|
|
102
|
+
* Stop the marquee animation
|
|
103
|
+
*/
|
|
17
104
|
stop: () => void;
|
|
105
|
+
/**
|
|
106
|
+
* Whether the animation is currently active
|
|
107
|
+
*/
|
|
18
108
|
isActive: boolean;
|
|
19
|
-
}
|
|
109
|
+
}
|
|
20
110
|
/**
|
|
21
|
-
*
|
|
111
|
+
* MarqueeText component for scrolling text or content
|
|
22
112
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
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
|
|
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.
|
|
33
116
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 {};
|
|
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,131 +1,25 @@
|
|
|
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
1
|
import * as React from 'react';
|
|
119
|
-
import { StyleSheet, View } from 'react-native';
|
|
2
|
+
import { StyleSheet, View, Text } from 'react-native';
|
|
120
3
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
121
|
-
import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle,
|
|
4
|
+
import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useSharedValue, useFrameCallback, withDecay, } from 'react-native-reanimated';
|
|
122
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
|
+
*/
|
|
123
17
|
var AnimatedChild = React.memo(function (_a) {
|
|
124
|
-
var index = _a.index, children = _a.children, anim = _a.anim,
|
|
125
|
-
var
|
|
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 () {
|
|
126
20
|
var _a;
|
|
127
21
|
var isVertical = direction === 'vertical';
|
|
128
|
-
var dimension = isVertical ?
|
|
22
|
+
var dimension = isVertical ? contentMeasurement.value.height : contentMeasurement.value.width;
|
|
129
23
|
if (dimension <= 0)
|
|
130
24
|
return {};
|
|
131
25
|
var position = (index - 1) * (dimension + spacing);
|
|
@@ -134,67 +28,59 @@ var AnimatedChild = React.memo(function (_a) {
|
|
|
134
28
|
position: 'absolute'
|
|
135
29
|
},
|
|
136
30
|
_a[isVertical ? 'top' : 'left'] = position,
|
|
137
|
-
_a.transform =
|
|
138
|
-
{
|
|
139
|
-
|
|
140
|
-
},
|
|
141
|
-
],
|
|
31
|
+
_a.transform = isVertical
|
|
32
|
+
? [{ translateY: translation }]
|
|
33
|
+
: [{ translateX: translation }],
|
|
142
34
|
_a;
|
|
143
|
-
}, [index, spacing,
|
|
144
|
-
return <Animated.View style={
|
|
35
|
+
}, [index, spacing, contentMeasurement, direction]);
|
|
36
|
+
return <Animated.View style={style}>{children}</Animated.View>;
|
|
145
37
|
});
|
|
146
38
|
/**
|
|
147
|
-
*
|
|
39
|
+
* MarqueeText component for scrolling text or content
|
|
148
40
|
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
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
|
|
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.
|
|
159
44
|
*/
|
|
160
|
-
|
|
161
|
-
var _b = _a.
|
|
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;
|
|
162
47
|
var isVertical = direction === 'vertical';
|
|
163
|
-
var
|
|
48
|
+
var isBounceMode = mode === AnimationMode.BOUNCE;
|
|
49
|
+
var containerMeasurement = useSharedValue({
|
|
164
50
|
width: 0,
|
|
165
51
|
height: 0,
|
|
166
52
|
x: 0,
|
|
167
53
|
y: 0,
|
|
168
54
|
});
|
|
169
|
-
var
|
|
55
|
+
var contentMeasurement = useSharedValue({
|
|
170
56
|
width: 0,
|
|
171
57
|
height: 0,
|
|
172
58
|
x: 0,
|
|
173
59
|
y: 0,
|
|
174
60
|
});
|
|
175
|
-
var
|
|
61
|
+
var _m = React.useState(0), cloneTimes = _m[0], setCloneTimes = _m[1];
|
|
176
62
|
var anim = useSharedValue(0);
|
|
177
63
|
var isMounted = React.useRef(true);
|
|
178
|
-
var
|
|
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
|
|
179
68
|
var frameCallback = useFrameCallback(function (frameInfo) {
|
|
180
|
-
if (frameInfo.timeSincePreviousFrame === null)
|
|
69
|
+
if (!enabled || frameInfo.timeSincePreviousFrame === null)
|
|
181
70
|
return;
|
|
182
|
-
var
|
|
71
|
+
var deltaTime = frameRateMs
|
|
183
72
|
? frameInfo.timeSincePreviousFrame / frameRateMs
|
|
184
|
-
:
|
|
185
|
-
anim.value += (reverse ? -1 : 1) *
|
|
73
|
+
: frameInfo.timeSincePreviousFrame;
|
|
74
|
+
anim.value += (reverse ? -1 : 1) * pixelsPerMs * deltaTime;
|
|
186
75
|
}, false);
|
|
187
|
-
|
|
188
|
-
if (position) {
|
|
189
|
-
position.value = anim.value;
|
|
190
|
-
}
|
|
191
|
-
});
|
|
76
|
+
// Calculate how many clones we need to fill the screen
|
|
192
77
|
useAnimatedReaction(function () {
|
|
193
|
-
var
|
|
194
|
-
var
|
|
195
|
-
if (
|
|
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)
|
|
196
81
|
return 0;
|
|
197
|
-
|
|
82
|
+
// Need enough clones to fill the container plus buffer for continuous scrolling
|
|
83
|
+
return Math.ceil(containerDim / contentDim) + 2;
|
|
198
84
|
}, function (times) {
|
|
199
85
|
if (times > 0 && isMounted.current) {
|
|
200
86
|
runOnJS(setCloneTimes)(times);
|
|
@@ -202,75 +88,121 @@ export var Marquee = React.memo(React.forwardRef(function (_a, ref) {
|
|
|
202
88
|
}, [direction]);
|
|
203
89
|
// Control functions
|
|
204
90
|
var start = React.useCallback(function () {
|
|
91
|
+
if (!enabled)
|
|
92
|
+
return;
|
|
205
93
|
frameCallback.setActive(true);
|
|
206
|
-
|
|
94
|
+
isActive.value = true;
|
|
95
|
+
onAnimationStart === null || onAnimationStart === void 0 ? void 0 : onAnimationStart();
|
|
96
|
+
}, [frameCallback, enabled, onAnimationStart]);
|
|
207
97
|
var stop = React.useCallback(function () {
|
|
208
98
|
frameCallback.setActive(false);
|
|
209
|
-
|
|
99
|
+
isActive.value = false;
|
|
100
|
+
onAnimationStop === null || onAnimationStop === void 0 ? void 0 : onAnimationStop();
|
|
101
|
+
}, [frameCallback, onAnimationStop]);
|
|
102
|
+
// Expose controls via ref
|
|
210
103
|
React.useImperativeHandle(ref, function () { return ({
|
|
211
104
|
start: start,
|
|
212
105
|
stop: stop,
|
|
213
|
-
isActive
|
|
106
|
+
get isActive() { return isActive.value; }
|
|
214
107
|
}); });
|
|
108
|
+
// Setup gesture handling
|
|
215
109
|
var pan = React.useMemo(function () {
|
|
216
110
|
return Gesture.Pan()
|
|
217
|
-
.enabled(withGesture)
|
|
218
|
-
.onBegin(function () {
|
|
111
|
+
.enabled(withGesture && enabled)
|
|
112
|
+
.onBegin(function () {
|
|
113
|
+
// Stop auto-animation when user interacts
|
|
114
|
+
runOnJS(stop)();
|
|
115
|
+
})
|
|
219
116
|
.onChange(function (e) {
|
|
117
|
+
// Move according to user's gesture
|
|
220
118
|
anim.value += -(isVertical ? e.changeY : e.changeX);
|
|
221
119
|
})
|
|
222
120
|
.onFinalize(function (e) {
|
|
121
|
+
// Apply momentum scrolling when user releases
|
|
223
122
|
anim.value = withDecay({
|
|
224
123
|
velocity: -(isVertical ? e.velocityY : e.velocityX),
|
|
225
|
-
}, function (finished) {
|
|
124
|
+
}, function (finished) {
|
|
125
|
+
// Restart auto-animation when decay finishes
|
|
126
|
+
if (finished)
|
|
127
|
+
runOnJS(start)();
|
|
128
|
+
});
|
|
226
129
|
});
|
|
227
|
-
}, [withGesture, isVertical, anim, start, stop]);
|
|
130
|
+
}, [withGesture, isVertical, anim, start, stop, enabled]);
|
|
228
131
|
// Handle focus/unmount
|
|
229
132
|
useFocusEffect(React.useCallback(function () {
|
|
230
|
-
|
|
133
|
+
// Start animation when screen comes into focus
|
|
134
|
+
if (enabled) {
|
|
135
|
+
start();
|
|
136
|
+
}
|
|
231
137
|
return function () {
|
|
138
|
+
// Stop animation when screen loses focus
|
|
232
139
|
stop();
|
|
233
140
|
anim.value = 0;
|
|
234
141
|
};
|
|
235
|
-
}, [start, stop, anim]));
|
|
142
|
+
}, [start, stop, anim, enabled]));
|
|
143
|
+
// Cleanup on unmount
|
|
236
144
|
React.useEffect(function () {
|
|
237
145
|
return function () {
|
|
238
146
|
isMounted.current = false;
|
|
239
147
|
};
|
|
240
148
|
}, []);
|
|
241
|
-
|
|
242
|
-
|
|
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;
|
|
243
171
|
}} pointerEvents="box-none">
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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;
|
|
249
177
|
}}>
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
Array.from({ length: cloneTimes }).map(function (_, index) { return (<AnimatedChild key={"clone-".concat(index)} index={index} anim={anim}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
})
|
|
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
|
+
});
|
|
262
190
|
var styles = StyleSheet.create({
|
|
191
|
+
container: {
|
|
192
|
+
overflow: 'hidden',
|
|
193
|
+
},
|
|
263
194
|
hidden: {
|
|
264
195
|
opacity: 0,
|
|
265
|
-
|
|
266
|
-
|
|
196
|
+
position: 'absolute',
|
|
197
|
+
zIndex: -1,
|
|
267
198
|
},
|
|
268
199
|
row: {
|
|
269
200
|
flexDirection: 'row',
|
|
270
|
-
|
|
201
|
+
position: 'relative',
|
|
271
202
|
},
|
|
272
203
|
column: {
|
|
273
204
|
flexDirection: 'column',
|
|
274
|
-
|
|
205
|
+
position: 'relative',
|
|
275
206
|
},
|
|
276
207
|
});
|
|
208
|
+
export { MarqueeText, AnimationMode };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AutoScroll } from './AutoScroll';
|
|
2
|
-
import {
|
|
2
|
+
import { MarqueeText } from './MarqueeText';
|
|
3
3
|
import { AnimationMode } from './constants';
|
|
4
|
-
export { AutoScroll, AnimationMode,
|
|
4
|
+
export { AutoScroll, AnimationMode, MarqueeText };
|
|
5
5
|
export default AutoScroll;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AutoScroll } from './AutoScroll';
|
|
2
|
-
import {
|
|
2
|
+
import { MarqueeText } from './MarqueeText';
|
|
3
3
|
import { AnimationMode } from './constants';
|
|
4
|
-
export { AutoScroll, AnimationMode,
|
|
4
|
+
export { AutoScroll, AnimationMode, MarqueeText };
|
|
5
5
|
export default AutoScroll;
|