0

So below is my code trying to make an animation bar

const ProgressBarInternal = ({
  color,
  backgroundColor,
  style,
  height,
  animDuration,
  total,
  progress,
  testID = 'progress-bar',
  borderRadius,
  containerHeight,
  onAnimationDidEnd,
}: Props): JSX.Element => {
  const barProgressPercentageNum = Math.max(0, Math.floor((progress / total) * 100))
  const barProgressPercentageString = `${barProgressPercentageNum}%`
  const translateX = useRef(new Animated.Value(0))
  useEffect(() => {
    Animated.timing(translateX.current, {
      toValue: 1,
      duration: animDuration || 950,
      easing: Easing.inOut(Easing.ease),
    }).start(() => onAnimationDidEnd)
  }, [])

  return (
    <View
      testID={testID}
      style={[
        styles.container,
        { borderRadius: borderRadius, height: containerHeight },
        height ? { height } : undefined,
        backgroundColor ? { backgroundColor } : undefined,
        style,
      ]}>
      <Animated.View
        style={[
          styles.bar,
          { borderRadius: borderRadius },
          {
            backgroundColor: color,
            width: barProgressPercentageString,
          },
          {
            transform: [
              {
                translateX: translateX.current.interpolate({
                  inputRange: [0, 1],
                  outputRange: ['0%', '100%'],
                }),
              },
            ],
          },
        ]}
      />
    </View>
  )
}

with above code, it does animation in a strange way -> moved the bar to the center of the bar holder enter image description here

what I expect to get is something like this : enter image description here

I think it is something to do with my translateX not setting up correctly? Please advise with some code sample Thanks

Edited

Following Marek's suggestions: I now have :

const ProgressBarInternal = ({
  color,
  backgroundColor,
  style,
  height,
  animDuration,
  total,
  progress,
  testID = 'progress-bar',
  borderRadius,
  containerHeight,
  onAnimationDidEnd,
}: Props): JSX.Element => {
  const barProgressPercentageNum = Math.max(0, Math.floor((progress / total) * 100))
  const barProgressPercentageString = `${barProgressPercentageNum}%`
  const animation = useRef(new Animated.Value(0))
  useEffect(() => {
    Animated.timing(translateX.current, {
      toValue: 1 ,
      duration: animDuration || 950,
      easing: Easing.inOut(Easing.ease),
    }).start(() => onAnimationDidEnd)
  }, [])

  return (
    <View
      testID={testID}
      style={[
        styles.container,
        { borderRadius: borderRadius, height: containerHeight },
        height ? { height } : undefined,
        backgroundColor ? { backgroundColor } : undefined,
        style,
      ]}>
      <Animated.View
        style={[
          styles.bar,
          { borderRadius: borderRadius },
          {
            backgroundColor: color,
//how do I pass my param -> barProgressPercentageNum to here?
            width: animation.current.interpolate({ inputRange: [0, 1], outputRange: ['0%', '100%']}),    
          },
          
        ]}
      />
    </View>
  )
}

enter image description here

I wish to pass in a width % param to the bar, so it only animate to my requested position, how would I do that? Thanks

2nd Edited

Please see the edited code with container style and animated style

import React, { useEffect, useRef } from 'react'
import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native'
import Animated, { Easing } from 'react-native-reanimated'

interface Props {
  total: number
  progress: number
  color?: string
  backgroundColor?: string
  height?: number
  style?: StyleProp<ViewStyle>
  animDelay?: number
  animDuration?: number
  testID?: string
  borderRadius?: number
  containerHeight?: number
  onAnimationDidEnd?: () => void
}

const ProgressBarInternal = ({
  color,
  backgroundColor,
  style,
  height,
  animDuration,
  total,
  progress,
  testID = 'progress-bar',
  borderRadius,
  containerHeight,
  onAnimationDidEnd,
}: Props): JSX.Element => {
  const barProgressPercentageNum = Math.max(0, Math.floor((progress / total) * 100))
  const barProgressPercentageString = `${barProgressPercentageNum}%`
  const translateX = useRef(new Animated.Value(0))
  useEffect(() => {
    Animated.timing(translateX.current, {
      toValue: 1,
      duration: animDuration || 950,
      easing: Easing.inOut(Easing.ease),
    }).start(() => onAnimationDidEnd)
  }, [])

  return (
    <View
      testID={testID}
      style={[
        styles.container,
        { borderRadius: borderRadius, height: containerHeight },
        height ? { height } : undefined,
        backgroundColor ? { backgroundColor } : undefined,        
        style,
      ]}>
      <Animated.View
        style={[
          styles.bar,
          { borderRadius: borderRadius },          
          {
            backgroundColor: color,
            width: translateX.current.interpolate({ inputRange: [0, 1], outputRange: ['0%', '100%']}),
          },
          
        ]}
      />
    </View>
  )
}

export default ProgressBarInternal

const styles = StyleSheet.create({
  bar: {
    height: '100%',
    width: '100%',
  },
  container: {
    flexDirection: 'row',
    overflow: 'hidden',
    width: '100%',
  },
})

It only fills 1/3 of the bar even I set to 100%...

sefirosu
  • 2,558
  • 7
  • 44
  • 69

1 Answers1

0

When you animate the translateX transform between 0% and 100%, moving it right by x% of its own width.

I guess what you want is to have the progress fill up the width of it's parent - in that case, you can remove the transform style property as well as the barProgressPercentageString, and instead just animate the width:

const animation = useRef(new Animated.Value(0)).current;

useEffect(() => {
   Animated.timing(translateX.current, {
      toValue: progress / total, // <-- 
      duration: animDuration || 950,
      easing: Easing.inOut(Easing.ease),
    }).start(() => onAnimationDidEnd)
}, []);

// in style.container, you might need:
width: '100%',

// in style object of Animated.View:
width: animation.interpolate({ inputRange: [0, 1], outputRange: ['0%', '100%']),
Marek Lisik
  • 2,003
  • 1
  • 8
  • 17
  • Hi Marek, thanks for the answer, I wish to pass in a param (width) from parent component, so the progress bar can move to a % width as that param defines. with above solution, I can only animated to width 1/3 of the bar even I set it to be 100% , please see screenshot. any advise? thanks – sefirosu Mar 29 '21 at 12:42
  • I'm not sure why it would just fill in 1/3 of the parent - can you also include the relevant style objects in your question (styles.container and styles.bar). – Marek Lisik Mar 29 '21 at 12:59
  • PS - I updated the answer to highlight that you should probably just pass the normalised progress value `progress / total` as the `toValue` of the animation config. I think `barProgressPercentageString` and `barProgressPercentageNum` are both unnecessary. – Marek Lisik Mar 29 '21 at 13:02
  • Hi Marek, thanks for the advise, please see the full version of my 2nd edited, I have included whole component code here, somehow it only fills 1/3 of the bar :( – sefirosu Mar 29 '21 at 13:30
  • Perhaps there are some impactful styles you pass ass props to this component? I created this snack https://snack.expo.io/@mlisik/stack-overflow---progress-bar that has your code with my modifications, and appears to work OK – Marek Lisik Mar 29 '21 at 13:43