Skip to content

Instantly share code, notes, and snippets.

@akinncar
Created March 16, 2022 11:45
Show Gist options
  • Save akinncar/65eacf9dee90a3ba7ed374298aabbac0 to your computer and use it in GitHub Desktop.
Save akinncar/65eacf9dee90a3ba7ed374298aabbac0 to your computer and use it in GitHub Desktop.
AutoScrolling horizontal component for React Native
import * as React from 'react'
import { Animated, Easing, ScrollView, View } from 'react-native'
const AutoScrolling = ({
style,
children,
endPaddingWidth = 20,
duration,
delay = 0,
isLTR = false,
disabled = false,
}) => {
const [isAutoScrollEnabled, setIsAutoScrollEnabled] = React.useState(false)
const [dividerWidth, setDividerWidth] = React.useState(endPaddingWidth)
const containerWidth = React.useRef(0)
const contentWidth = React.useRef(0)
const offsetX = React.useRef(new Animated.Value(0))
const contentRef = React.useRef(null)
React.useEffect(() => {
return () => {
contentRef.current = null
}
})
const checkContent = React.useCallback(
(newContentWidth, fx) => {
if (!newContentWidth) {
setIsAutoScrollEnabled(false)
return
}
if (contentWidth.current === newContentWidth) return
contentWidth.current = newContentWidth
let newDividerWidth = endPaddingWidth
if (contentWidth.current < containerWidth.current) {
if (endPaddingWidth < containerWidth.current - contentWidth.current) {
newDividerWidth = containerWidth.current - contentWidth.current
}
}
setDividerWidth(newDividerWidth)
setIsAutoScrollEnabled(true)
if (isLTR) {
offsetX.current.setValue(-(newContentWidth + newDividerWidth))
}
Animated.loop(
Animated.timing(offsetX.current, {
toValue: isLTR ? fx : -(contentWidth.current + fx + newDividerWidth),
duration: duration || 50 * contentWidth.current,
delay,
easing: Easing.linear,
useNativeDriver: true,
})
).start()
},
[delay, duration, endPaddingWidth, isLTR]
)
const measureContainerView = React.useCallback(
({
nativeEvent: {
layout: { width },
},
}) => {
if (containerWidth.current === width) return
containerWidth.current = width
if (!contentRef.current) return
contentRef.current.measure((fx, _fy, width) => {
checkContent(width, fx)
})
},
[checkContent, contentRef]
)
const childrenCloned = React.useMemo(
() =>
React.cloneElement(children, {
...children.props,
onLayout: ({
nativeEvent: {
layout: { width, x },
},
}) => {
if (!containerWidth.current || width === contentWidth.current) return
offsetX.current.stopAnimation()
offsetX.current.setValue(0)
offsetX.current.setOffset(0)
checkContent(width, x)
},
ref: (ref) => (contentRef.current = ref),
}),
[checkContent, children, contentRef]
)
if (disabled) return <>{children}</>
return (
<View onLayout={measureContainerView} style={style}>
<ScrollView
horizontal
bounces={false}
scrollEnabled={false}
showsHorizontalScrollIndicator={false}
>
<Animated.View
style={[{ flexDirection: 'row' }, { transform: [{ translateX: offsetX.current }] }]}
>
{childrenCloned}
{isAutoScrollEnabled && (
<>
<View style={{ width: dividerWidth }} />
{children}
</>
)}
</Animated.View>
</ScrollView>
</View>
)
}
export default React.memo(AutoScrolling)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment