feat: 初始提交(仅核心代码,已排除大文件)
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import {
|
||||
Animated,
|
||||
Easing,
|
||||
LayoutChangeEvent,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextProps,
|
||||
TextStyle,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
|
||||
interface MarqueeTextProps extends Omit<TextProps, 'style'> {
|
||||
text: string;
|
||||
enabled?: boolean;
|
||||
style?: StyleProp<TextStyle>;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
gap?: number;
|
||||
speed?: number;
|
||||
initialDelayMs?: number;
|
||||
}
|
||||
|
||||
export const MarqueeText: React.FC<MarqueeTextProps> = ({
|
||||
text,
|
||||
enabled = false,
|
||||
style,
|
||||
containerStyle,
|
||||
gap = 40,
|
||||
speed = 28,
|
||||
initialDelayMs = 1200,
|
||||
...textProps
|
||||
}) => {
|
||||
const translateX = useRef(new Animated.Value(0)).current;
|
||||
const animationRef = useRef<Animated.CompositeAnimation | null>(null);
|
||||
const [containerWidth, setContainerWidth] = useState(0);
|
||||
const [textWidth, setTextWidth] = useState(0);
|
||||
|
||||
const normalizedText = useMemo(() => String(text || ''), [text]);
|
||||
const shouldAnimate = enabled && containerWidth > 0 && textWidth > containerWidth + 4;
|
||||
const distance = Math.max(0, textWidth + gap);
|
||||
const durationMs = Math.max(6000, Math.round((distance / Math.max(speed, 1)) * 1000));
|
||||
|
||||
useEffect(() => {
|
||||
animationRef.current?.stop();
|
||||
translateX.stopAnimation();
|
||||
translateX.setValue(0);
|
||||
|
||||
if (!shouldAnimate || !distance) {
|
||||
return () => {
|
||||
animationRef.current?.stop();
|
||||
};
|
||||
}
|
||||
|
||||
const sequence = Animated.loop(
|
||||
Animated.sequence([
|
||||
Animated.delay(initialDelayMs),
|
||||
Animated.timing(translateX, {
|
||||
toValue: -distance,
|
||||
duration: durationMs,
|
||||
easing: Easing.linear,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
Animated.delay(700),
|
||||
Animated.timing(translateX, {
|
||||
toValue: 0,
|
||||
duration: 0,
|
||||
useNativeDriver: true,
|
||||
}),
|
||||
])
|
||||
);
|
||||
|
||||
animationRef.current = sequence;
|
||||
sequence.start();
|
||||
|
||||
return () => {
|
||||
sequence.stop();
|
||||
animationRef.current = null;
|
||||
translateX.stopAnimation();
|
||||
translateX.setValue(0);
|
||||
};
|
||||
}, [distance, durationMs, initialDelayMs, shouldAnimate, translateX]);
|
||||
|
||||
const handleContainerLayout = (event: LayoutChangeEvent) => {
|
||||
const nextWidth = Math.round(event.nativeEvent.layout.width || 0);
|
||||
if (nextWidth !== containerWidth) {
|
||||
setContainerWidth(nextWidth);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTextLayout = (event: LayoutChangeEvent) => {
|
||||
const nextWidth = Math.round(event.nativeEvent.layout.width || 0);
|
||||
if (nextWidth !== textWidth) {
|
||||
setTextWidth(nextWidth);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.container, containerStyle]} onLayout={handleContainerLayout}>
|
||||
<Text
|
||||
{...textProps}
|
||||
style={[style, styles.measureText]}
|
||||
onLayout={handleTextLayout}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{normalizedText}
|
||||
</Text>
|
||||
{shouldAnimate ? (
|
||||
<Animated.View style={[styles.track, { transform: [{ translateX }] }]}>
|
||||
<Text {...textProps} style={style} numberOfLines={1}>
|
||||
{normalizedText}
|
||||
</Text>
|
||||
<View style={{ width: gap }} />
|
||||
<Text {...textProps} style={style} numberOfLines={1}>
|
||||
{normalizedText}
|
||||
</Text>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<Text {...textProps} style={style} numberOfLines={1} ellipsizeMode="tail">
|
||||
{normalizedText}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
track: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
measureText: {
|
||||
position: 'absolute',
|
||||
opacity: 0,
|
||||
left: 0,
|
||||
top: -9999,
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user