Skip to content

Instantly share code, notes, and snippets.

@smontlouis
Created October 30, 2024 12:21
Show Gist options
  • Save smontlouis/3a34ba5fb1e188b86bd3fb9b7ae5627e to your computer and use it in GitHub Desktop.
Save smontlouis/3a34ba5fb1e188b86bd3fb9b7ae5627e to your computer and use it in GitHub Desktop.
Animated audio lines
import { UseThemeResult } from '@tamagui/core'
import React from 'react'
import Animated, {
Easing,
SharedValue,
interpolateColor,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withRepeat,
withSequence,
withTiming,
} from 'react-native-reanimated'
import { usePlaybackButtonMode } from 'src/hooks/usePlaybackButtonMode'
import { XStack, useTheme } from 'tamagui'
const BAR_COUNT = 5
export function AnimatedAudioLines({
size = 18,
activeColor = 'color9',
inactiveColor = 'neutral9',
}: {
size?: number
activeColor?: keyof UseThemeResult | string
inactiveColor?: keyof UseThemeResult | string
}) {
const buttonMode = usePlaybackButtonMode()
const isPlaying = buttonMode === 'playing'
const barHeight = 16 * (size / 18)
const barWidth = 2.5 * (size / 18)
const theme = useTheme()
const activeColorValue: string = theme[activeColor]
? theme[activeColor]?.get()
: activeColor
const inactiveColorValue: string = theme[inactiveColor]
? theme[inactiveColor]?.get()
: inactiveColor
const colorProgress = useSharedValue(isPlaying ? 1 : 0)
React.useEffect(() => {
colorProgress.value = withTiming(isPlaying ? 1 : 0, { duration: 300 })
}, [isPlaying, colorProgress])
const animatedColor = useDerivedValue(() => {
return interpolateColor(
colorProgress.value,
[0, 1],
[inactiveColorValue, activeColorValue]
)
})
return (
<XStack
gap="$1"
width={size}
height={size}
alignItems="center"
justifyContent="center"
>
{Array.from({ length: BAR_COUNT }).map((_, index) => (
<Bar
key={index}
index={index}
isPlaying={isPlaying}
barHeight={barHeight}
barWidth={barWidth}
color={animatedColor}
/>
))}
</XStack>
)
}
interface BarProps {
index: number
isPlaying: boolean
barHeight: number
barWidth: number
color: SharedValue<string>
}
const heightPatterns = [
[0.2, 0.3, 1.0, 0.3, 0.2],
[0.2, 1.0, 0.5, 0.3, 0.2],
[0.2, 0.3, 0.5, 1.0, 0.2],
[0.3, 0.5, 0.3, 0.2, 0.6],
[1.0, 0.5, 0.3, 0.2, 0.3],
[0.2, 0.4, 1.0, 0.4, 0.2],
[0.3, 1.0, 0.5, 0.2, 0.3],
[0.3, 0.2, 0.5, 1.0, 0.3],
[0.2, 0.3, 0.4, 0.2, 0.3],
[1.0, 0.5, 0.2, 0.3, 0.2],
]
function Bar({ index, isPlaying, barHeight, barWidth, color }: BarProps) {
const progress = useSharedValue(0)
React.useEffect(() => {
if (isPlaying) {
const animations = heightPatterns.map((pattern) =>
withTiming(pattern[index], {
duration: 300,
easing: Easing.linear,
})
)
progress.value = withRepeat(withSequence(...animations), -1, true)
} else {
progress.value = withTiming(0, { duration: 400 })
}
}, [isPlaying, index, progress])
const animatedStyle = useAnimatedStyle(() => {
const height = progress.value * barHeight
return {
height: height < barWidth ? barWidth : height,
width: barWidth,
borderRadius: barWidth / 2,
backgroundColor: color.value,
}
})
return <Animated.View style={[animatedStyle]} />
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment