react-native-marquee-text-loop
Version:
A smooth marquee text component for react native.
134 lines (126 loc) • 4.1 kB
JavaScript
"use strict";
import * as React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useFrameCallback, useSharedValue } from 'react-native-reanimated';
// Clone of the marquee text
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const ScrollingTextClone = ({
index,
children,
anim,
textLayout
}) => {
// Animated styles to position the cloned text
const styles = useAnimatedStyle(() => {
return {
position: 'absolute',
left: (index - 1) * (textLayout.value.width + 10),
// Spacing after each clone text
transform: [{
translateX: -(anim.value % (textLayout.value.width + 10) // Scroll the text horizontally
)
}]
};
}, [index, textLayout]);
// Return the cloned text element with animated styles
return /*#__PURE__*/_jsx(Animated.View, {
style: styles,
children: children
});
};
// Marquee text component
export const MarqueeText = /*#__PURE__*/React.memo(({
speed = 1,
text = '',
textStyles = {}
}) => {
// Store measurements of the parent layout and text
const parentLayout = useSharedValue({
width: 0,
height: 0,
x: 0,
y: 0
});
const textLayout = useSharedValue({
width: 0,
height: 0,
x: 0,
y: 0
});
// number clones of the text are needed for scrolling
const [numberOfClones, setNumberOfClones] = React.useState(0);
// animated value for the scrolling animation
const scrollAnimationValue = useSharedValue(0);
const frameRateMs = 30; // frame rate
// update the animation on each animation
useFrameCallback(frameInfo => {
if (frameInfo.timeSincePreviousFrame === null) {
return;
}
const frameDelta = frameInfo.timeSincePreviousFrame / frameRateMs;
scrollAnimationValue.value += speed * frameDelta; // Update the scroll animation
}, true);
// update the number of clones based on text size
useAnimatedReaction(() => {
// If the text or parent container size is zero - zero clones
if (textLayout.value.width === 0 || parentLayout.value.width === 0 || textLayout.value.height === 0 || parentLayout.value.height === 0) {
return 0;
}
// Calculate the number of clones needed based on the parent layout
return Math.round(parentLayout.value.width / textLayout.value.width) + 1 // Adding extra clones to ensure the loop continues
;
}, v => {
if (v === 0) {
return; // No clones if the measurement values are zero
}
runOnJS(setNumberOfClones)(v + 2); // Set the clone count in JS thread (runOnJS avoids Reanimated thread issues)
}, []);
return /*#__PURE__*/_jsx(Animated.View, {
onLayout: ev => {
parentLayout.value = ev.nativeEvent.layout; // dimensions of the parent container
},
pointerEvents: "box-none",
children: /*#__PURE__*/_jsxs(Animated.View, {
style: styles.row,
pointerEvents: "box-none",
children: [/*#__PURE__*/_jsx(Animated.ScrollView, {
horizontal: true,
style: styles.scrollContainer,
pointerEvents: "box-none",
children: /*#__PURE__*/_jsx(View, {
onLayout: ev => {
textLayout.value = ev.nativeEvent.layout; // width of the text
},
children: /*#__PURE__*/_jsx(Text, {
style: textStyles,
children: text
})
})
}), numberOfClones > 0 &&
// Render the clones
[...Array(numberOfClones).keys()].map(index => {
return /*#__PURE__*/_jsx(ScrollingTextClone, {
index: index,
anim: scrollAnimationValue,
textLayout: textLayout,
children: /*#__PURE__*/_jsx(Text, {
style: textStyles,
children: text
})
}, `clone-${index}`);
})]
})
});
});
const styles = StyleSheet.create({
scrollContainer: {
opacity: 0,
zIndex: -9999
},
// invisible scrollView
row: {
flexDirection: 'row',
overflow: 'hidden'
} //cloned texts in a horizontal row
});
//# sourceMappingURL=index.js.map