Please provide the following:
- SDK Version: 49
- Platforms: mobile
I’m working on a bar chart component and aiming to implement a neat “counting effect” by having the numbers associated with each bar increment or decrement by 0.1. The idea is to create a visually appealing transition as the data changes.
However, I’ve run into a snag. When I set the increment value at 0.1 or even up to 0.3, the component starts to lag. It’s only at 0.4-0.5 where it doesn’t lag. Here’s the code snippet where this is happening:
if (displayValue < targetValues[index]) { return Math.min(displayValue + 0.1, targetValues[index]); } else if (displayValue > targetValues[index]) { return Math.max(displayValue - 0.1, targetValues[index]); }
I’ve been stuck on this for a while now and am wondering if there’s a smarter or more efficient way to achieve this counting effect without the lag. This is my first post on any forum since i like to solve problems on my own, but now I’m desperate for new ideas. Any suggestions or alternative approaches would be hugely appreciated!
Thanks a ton in advance for any help or insights!
import React, { useState, useEffect, useMemo } from "react";
import {
View,
Text,
Button,
Dimensions,
StyleSheet,
TouchableOpacity,
} from "react-native";
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming,
} from "react-native-reanimated";
import { AntDesign } from "@expo/vector-icons";
import { LinearGradient } from "expo-linear-gradient";
import { AppRegistry } from "react-native";
const initialBlackDisplayValues = [0, 0, 0, 0, 0, 0];
const initialDisplayValues = [0, 0, 0, 0, 0, 0];
function App() {
const { height, width } = Dimensions.get("window");
const colors2 = [
["#ff0000ff", "#ff9900ff"],
["#ff8c00ff", "#fff200ff"],
["#04ff00ff", "#17b988ff"],
["#00ffeaff", "#059ce2ff"],
["#007bffff", "#8800ffff"],
["#9900ffff", "#ff00aeff"],
];
const colors3 = [
["#ff00009d", "#ff99009d"],
["#ff8c009d", "#fff2009d"],
["#04ff009d", "#17b9889d"],
["#00ffea9d", "#059ce29d"],
["#007bff9d", "#8800ff9d"],
["#9900ff9d", "#ff00ae9d"],
];
const [displayValues, setDisplayValues] = useState(initialDisplayValues);
const [targetValues, setTargetValues] = useState(initialDisplayValues);
const animations = displayValues.map(() => useSharedValue(0));
const [blackDisplayValues, setBlackDisplayValues] = useState(
initialBlackDisplayValues
);
const [blackTargetValues, setBlackTargetValues] = useState(
initialBlackDisplayValues
);
const blackAnimations = blackDisplayValues.map(() => useSharedValue(0));
const opacity = useSharedValue(0);
const blackTextOpacity = useSharedValue(0);
const [blackBarsVisible, setBlackBarsVisible] = useState(false);
const [buttonDisabled, setButtonDisabled] = useState(false);
const [averageManOn, setAvrageManOn] = useState(false);
const shouldUpdateDisplayValues = useMemo(() => {
return !targetValues.every(
(value, index) => value === displayValues[index]
);
}, [targetValues, displayValues]);
const shouldUpdateBlackDisplayValues = useMemo(() => {
return !blackTargetValues.every(
(value, index) => value === blackDisplayValues[index]
);
}, [blackTargetValues, blackDisplayValues]);
useEffect(() => {
if (shouldUpdateDisplayValues) {
const interval = setInterval(() => {
const newDisplayValues = displayValues.map((displayValue, index) => {
if (displayValue < targetValues[index]) {
return Math.min(displayValue + 0.1, targetValues[index]);
} else if (displayValue > targetValues[index]) {
return Math.max(displayValue - 0.1, targetValues[index]);
} else {
return displayValue;
}
});
setDisplayValues(newDisplayValues);
}, 30);
return () => clearInterval(interval);
}
}, [targetValues, displayValues, shouldUpdateDisplayValues]);
useEffect(() => {
if (shouldUpdateBlackDisplayValues) {
const interval = setInterval(() => {
const newBlackDisplayValues = blackDisplayValues.map(
(displayValue, index) => {
if (displayValue < blackTargetValues[index]) {
return Math.min(displayValue + 0.1, blackTargetValues[index]);
} else if (displayValue > blackTargetValues[index]) {
return Math.max(displayValue - 0.1, blackTargetValues[index]);
} else {
return displayValue;
}
}
);
setBlackDisplayValues(newBlackDisplayValues);
}, 30);
return () => clearInterval(interval);
}
}, [blackTargetValues, blackDisplayValues, shouldUpdateBlackDisplayValues]);
useEffect(() => {
displayValues.forEach((displayValue, index) => {
animations[index].value = withSpring(displayValue / 10, {
damping: 100,
stiffness: 2000,
mass: 2,
});
});
}, [displayValues]);
const animatedStyles = animations.map((animation, index) =>
useAnimatedStyle(() => {
return {
width: animation.value * (width * 0.77),
// Change this to interpolate the color based on the opacity value
};
})
);
const toggleBlackValues = () => {
if (buttonDisabled) {
return;
}
setButtonDisabled(true);
setTimeout(() => setButtonDisabled(false), 1500);
if (blackBarsVisible) {
blackTextOpacity.value = withTiming(0, {
duration: 1000,
});
setTimeout(() => {
opacity.value = withTiming(0, {
duration: 1000,
});
}, 600);
setBlackTargetValues([0, 0, 0, 0, 0, 0]);
}
if (!blackBarsVisible) {
opacity.value = withTiming(1, {
duration: 700,
});
setTimeout(() => {
setBlackTargetValues([1.9, 9.2, 1.2, 1.5, 2.3, 1.8]);
blackTextOpacity.value = 1;
}, 750);
}
setBlackBarsVisible(!blackBarsVisible);
setTimeout(() => {
setAvrageManOn(!averageManOn);
}, 500);
};
useEffect(() => {
blackDisplayValues.forEach((displayValue, index) => {
blackAnimations[index].value = withSpring(displayValue / 10, {
damping: 100,
stiffness: 2000,
mass: 2,
});
});
}, [blackDisplayValues]);
const blackAnimatedStyles = blackAnimations.map((animation, index) =>
useAnimatedStyle(() => {
return {
width: animation.value * (width * 0.77),
position: "absolute",
display: animation.value === 0 ? "none" : "flex",
};
})
);
const blackTextAnimatedStyles = blackAnimations.map((animation, index) =>
useAnimatedStyle(() => {
return {
position: "absolute",
left: animation.value * (width * 0.77) + width * 0.01,
opacity: blackTextOpacity.value,
};
})
);
const GradientAnimatedStyles = blackAnimations.map((animation, index) =>
useAnimatedStyle(() => {
return {
opacity: opacity.value,
};
})
);
return (
<View style={styles.container0}>
<Animated.View style={styles.container}>
<View style={styles.conatiner3}>
<Animated.View>
<TouchableOpacity
onPress={toggleBlackValues}
style={styles.customButton}
activeOpacity={1}
>
<AntDesign
name="question"
size={height * 0.03}
color={blackBarsVisible ? "gray" : "white"}
/>
<Text
style={[
styles.customButtonText,
{ color: blackBarsVisible ? "gray" : "white" },
]}
>
Press here for comparison
</Text>
</TouchableOpacity>
</Animated.View>
</View>
<Animated.View style={styles.container2}>
{animatedStyles.map((animatedStyle, index) => (
<View key={index}>
<View style={styles.statContainer}></View>
<View style={styles.row}>
<View style={styles.border}>
<View style={styles.textConatiner}>
<Text style={styles.text}>
{displayValues[index].toFixed(1)}
</Text>
</View>
<View style={styles.innerView}>
<Animated.View style={[styles.animatedView, animatedStyle]}>
<Animated.View
style={[
GradientAnimatedStyles,
{
backgroundColor: "#7c7c7cff",
width: "100%",
height: "100%",
position: "absolute",
zIndex: 2,
},
]}
></Animated.View>
<LinearGradient
// Array of colors for gradient
colors={colors2[index]}
// Where the gradient starts
start={{ x: 0, y: 0 }}
// Where the gradient ends
end={{ x: 1, y: 1 }}
style={styles.background}
/>
</Animated.View>
<Animated.View
style={[
styles.blackAnimatedView,
blackAnimatedStyles[index],
]}
>
<LinearGradient
// Array of colors for gradient
colors={colors3[index]}
// Where the gradient starts
start={{ x: 0, y: 0 }}
// Where the gradient ends
end={{ x: 1, y: 1 }}
style={styles.background}
/>
</Animated.View>
<Animated.View style={blackTextAnimatedStyles[index]}>
<Text style={styles.blackText}>
{blackDisplayValues[index].toFixed(1)}
</Text>
</Animated.View>
</View>
</View>
</View>
</View>
))}
</Animated.View>
<View>
<Button
title="Button 1"
onPress={() => setTargetValues([9, 8, 1, 3, 2, 6.5])}
></Button>
<Button
title="Button 2"
onPress={() => setTargetValues([2, 4, 7, 6, 9, 5])}
></Button>
</View>
</Animated.View>
</View>
);
}
AppRegistry.registerComponent("main", () => App);
export default App;
const { height, width } = Dimensions.get("window");
const styles = StyleSheet.create({
container0: {
alignSelf: "center",
backgroundColor: "black",
flex: 1,
borderColor: "red",
borderWidth: 1,
width: "100%",
height: "100%",
maxWidth: 800, // Sätter en maxbredd på 800 pixlar
minHeight: 700,
},
container: {
flex: 1,
justifyContent: "flex-end",
paddingLeft: "2%",
paddingRight: "2%",
backgroundColor: "black",
borderColor: "green",
borderWidth: 1,
},
container2: {
height: "29%",
backgroundColor: "black",
padding: "1%",
justifyContent: "space-around",
borderColor: "#555a6c",
borderWidth: 0,
borderTopWidth: 2,
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
opacity: 1,
},
row: {
flexDirection: "row",
alignItems: "center",
borderColor: "blue",
borderWidth: 0,
justifyContent: "flex-start",
},
innerView: {
height: "100%",
width: width * 0.77,
backgroundColor: "#e0e0e0",
alignItems: "flex-start",
justifyContent: "center",
borderLeftWidth: height * 0.002,
borderColor: "#555a6c",
},
animatedView: {
height: "100%",
zIndex: 0,
position: "absolute",
},
blackAnimatedView: {
height: "100%",
zIndex: 2,
},
blackText: {
color: "black",
fontSize: height * 0.013,
fontWeight: "600",
marginTop: -width * 0.0015,
},
text: {
fontSize: height * 0.013,
color: "white",
marginTop: -width * 0.0015,
},
textConatiner: {
position: "absolute",
left: "1.8%",
borderColor: "pink",
borderWidth: 0,
height: 20,
width: "5.5%",
justifyContent: "center",
alignItems: "flex-start",
},
customButton: {
paddingTop: height * 0.01,
borderRadius: 5,
alignItems: "center",
justifyContent: "center", // Lägg till denna rad
textAlign: "center",
},
customButtonText: {
color: "white",
fontSize: height * 0.01,
},
background: {
width: "100%",
height: "100%",
},
statContainer: {
alignItems: "center",
borderColor: "red",
borderWidth: 0,
paddingTop: 0,
justifyContent: "flex-end",
},
statText: {
color: "white",
fontSize: height * 0.012,
},
border: {
flexDirection: "row",
height: height * 0.02,
width: width * 0.85,
borderWidth: height * 0.002,
borderColor: "#555a6c",
alignItems: "center",
justifyContent: "flex-end",
},
});
{
"name": "lifepath",
"version": "1.0.0",
"main": "App.js",
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"build": "npx expo export:web"
},
"dependencies": {
"@expo/vector-icons": "^13.0.0",
"@react-native-community/masked-view": "^0.1.11",
"expo": "^49.0.8",
"expo-linear-gradient": "~12.3.0",
"expo-navigation-bar": "~2.3.0",
"expo-updates": "~0.18.13",
"install": "^0.13.0",
"npx": "^3.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-native": "0.72.4",
"react-native-reanimated": "~3.3.0",
"react-native-screens": "~3.22.0"
},
"devDependencies": {
"@babel/core": "^7.20.0"
},
"private": true
}