diff --git a/index.tsx b/index.tsx index 79e0bc4..a9b5630 100644 --- a/index.tsx +++ b/index.tsx @@ -35,6 +35,11 @@ export interface MarqueeTextProps extends TextProps { * A callback for when the marquee finishes animation and stops */ onMarqueeComplete?: () => void; + /** + * A flag to enable consecutive mode that imitates the default behavior of HTML marquee element + * Does not take effect if loop is false + */ + consecutive?: boolean; } export interface MarqueeTextHandles { @@ -56,6 +61,10 @@ const createAnimation = ( loop: boolean; delay: number; }, + consecutive?: { + resetToValue: number; + duration: number; + }, ): Animated.CompositeAnimation => { const baseAnimation = Animated.timing(animatedValue, { easing: Easing.linear, @@ -64,6 +73,26 @@ const createAnimation = ( }); if (config.loop) { + if (consecutive) { + return Animated.sequence([ + baseAnimation, + Animated.loop( + Animated.sequence([ + Animated.timing(animatedValue, { + toValue: consecutive.resetToValue, + duration: 0, + useNativeDriver: true, + }), + Animated.timing(animatedValue, { + easing: Easing.linear, + useNativeDriver: true, + ...config, + duration: consecutive.duration, + }), + ]), + ), + ]); + } return Animated.loop(Animated.sequence([baseAnimation, Animated.delay(1000)])); } @@ -77,6 +106,7 @@ const MarqueeText = (props: MarqueeTextProps, ref: Ref): JSX speed = 1, loop = true, delay = 0, + consecutive = false, onMarqueeComplete, children, ...restProps @@ -96,11 +126,13 @@ const MarqueeText = (props: MarqueeTextProps, ref: Ref): JSX speed: number; loop: boolean; delay: number; + consecutive: boolean; }>({ marqueeOnStart, speed, loop, delay, + consecutive, }); const stopAnimation = useCallback(() => { @@ -125,11 +157,22 @@ const MarqueeText = (props: MarqueeTextProps, ref: Ref): JSX return; } - animation.current = createAnimation(animatedValue.current, { - ...config.current, - toValue: -distance, - duration: PixelRatio.getPixelSizeForLayoutSize(marqueeTextWidth.current) / config.current.speed, - }); + const baseDuration = PixelRatio.getPixelSizeForLayoutSize(marqueeTextWidth.current) / config.current.speed; + const { consecutive } = config.current; + animation.current = createAnimation( + animatedValue.current, + { + ...config.current, + toValue: consecutive ? -marqueeTextWidth.current : -distance, + duration: consecutive ? baseDuration * (marqueeTextWidth.current / distance) : baseDuration, + }, + consecutive + ? { + resetToValue: containerWidth.current, + duration: baseDuration * ((containerWidth.current + marqueeTextWidth.current) / distance), + } + : undefined, + ); animation.current.start((): void => { setIsAnimating(false); @@ -169,7 +212,7 @@ const MarqueeText = (props: MarqueeTextProps, ref: Ref): JSX const measureWidth = (component: ScrollView | Text): Promise => new Promise(resolve => { - UIManager.measure(findNodeHandle(component), (x: number, y: number, w: number) => { + UIManager.measure(findNodeHandle(component), (_x: number, _y: number, w: number) => { return resolve(w); }); });