@animatereactnative/marquee
Version:
A React Native Marquee component
160 lines (159 loc) • 4.79 kB
JavaScript
import * as React from 'react';
import { StyleSheet, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { runOnJS, useAnimatedReaction, useAnimatedStyle, useDerivedValue, useFrameCallback, useSharedValue, withDecay } from 'react-native-reanimated';
const AnimatedChild = ({
index,
children,
anim,
textMeasurement,
spacing,
direction
}) => {
const stylez = useAnimatedStyle(() => {
if (direction === 'vertical') {
return {
position: 'absolute',
top: (index - 1) * (textMeasurement.value.height + spacing),
transform: [{
translateY: -(anim.value % (textMeasurement.value.height + spacing))
}]
};
}
return {
position: 'absolute',
left: (index - 1) * (textMeasurement.value.width + spacing),
transform: [{
translateX: -(anim.value % (textMeasurement.value.width + spacing))
}]
};
}, [index, spacing, textMeasurement]);
return /*#__PURE__*/React.createElement(Animated.View, {
style: stylez
}, children);
};
/**
* Used to animate the given children in a horizontal manner.
*/
export const Marquee = /*#__PURE__*/React.memo(/*#__PURE__*/React.forwardRef(({
speed = 1,
children,
spacing = 0,
style,
reverse,
frameRate,
direction = 'horizontal',
position,
withGesture = true
}, ref) => {
const parentMeasurement = useSharedValue({
width: 0,
height: 0,
x: 0,
y: 0
});
const textMeasurement = useSharedValue({
width: 0,
height: 0,
x: 0,
y: 0
});
const [cloneTimes, setCloneTimes] = React.useState(0);
const anim = useSharedValue(0);
const frameRateMs = frameRate ? 1000 / frameRate : null;
const frameCallback = useFrameCallback(frameInfo => {
if (frameInfo.timeSincePreviousFrame === null) return;
const frameDelta = frameRateMs ? frameInfo.timeSincePreviousFrame / frameRateMs : 1;
if (reverse) {
anim.value -= speed * frameDelta;
} else {
anim.value += speed * frameDelta;
}
}, true);
useDerivedValue(() => {
if (position) {
position.value = anim.value;
}
});
useAnimatedReaction(() => {
if (textMeasurement.value.width === 0 || parentMeasurement.value.width === 0 || textMeasurement.value.height === 0 || parentMeasurement.value.height === 0) {
return 0;
}
return Math.round(direction === 'horizontal' ? parentMeasurement.value.width / textMeasurement.value.width : parentMeasurement.value.height / textMeasurement.value.height) + 1;
}, v => {
if (v === 0) {
return;
}
// This is going to cover the case when the text/element size
// is greater than the actual parent size
// Double this to cover the entire screen twice, in this way we can
// reset the position of the first element when its going to move out
// of the screen without any noticible glitch
runOnJS(setCloneTimes)(v + 2);
}, [direction]);
// Pan Gestures
function start() {
frameCallback.setActive(true);
}
function stop() {
frameCallback.setActive(false);
}
React.useImperativeHandle(ref, () => ({
start,
stop,
isActive: frameCallback.isActive
}));
const pan = Gesture.Pan().enabled(withGesture).onBegin(() => {
runOnJS(stop)();
}).onChange(e => {
anim.value += -(direction === 'horizontal' ? e.changeX : e.changeY);
}).onFinalize(e => {
anim.value = withDecay({
velocity: -(direction === 'horizontal' ? e.velocityX : e.velocityY)
}, finished => {
if (finished) {
runOnJS(start)();
}
});
});
return /*#__PURE__*/React.createElement(Animated.View, {
key: direction,
style: style,
onLayout: ev => {
parentMeasurement.value = ev.nativeEvent.layout;
},
pointerEvents: "box-none"
}, /*#__PURE__*/React.createElement(GestureDetector, {
gesture: pan
}, /*#__PURE__*/React.createElement(Animated.View, {
style: styles.row,
pointerEvents: "box-none"
}, /*#__PURE__*/React.createElement(Animated.ScrollView, {
horizontal: direction === 'horizontal',
style: styles.hidden,
pointerEvents: "box-none"
}, /*#__PURE__*/React.createElement(View, {
onLayout: ev => {
textMeasurement.value = ev.nativeEvent.layout;
}
}, children)), cloneTimes > 0 && [...Array(cloneTimes).keys()].map(index => {
return /*#__PURE__*/React.createElement(AnimatedChild, {
key: `clone-${index}`,
index: index,
anim: anim,
textMeasurement: textMeasurement,
spacing: spacing,
direction: direction
}, children);
}))));
}));
const styles = StyleSheet.create({
hidden: {
opacity: 0,
zIndex: -9999
},
row: {
flexDirection: 'row'
}
});
//# sourceMappingURL=index.js.map