SlidingNumber
An animated number component that slides digits up and down like a mechanical counter.
9:41
signal_cellular_altwifibattery_full
Current ARR:
$.
0
01234567890
01234567890
01234567890
01234567890
0123456789Installation
$
Usage
Example.tsx
import { SlidingNumber } from './components/SlidingNumber';export default function App() {return (<SlidingNumber value={123.45} fontStyle={{ fontSize: 32, fontWeight: 'bold' }} />);}
Full Component Code
SlidingNumber.tsx
1import React, { useEffect, useState } from 'react';2import { View, Text, StyleSheet, LayoutChangeEvent } from 'react-native';3import Animated, { useSharedValue, useAnimatedStyle, withSpring, useDerivedValue } from 'react-native-reanimated';45const Digit = ({ value, place, height, fontStyle }) => {6 const valueRoundedToPlace = Math.floor(value / place) % 10;7 const animatedValue = useSharedValue(valueRoundedToPlace);89 useEffect(() => {10 animatedValue.value = withSpring(valueRoundedToPlace, { stiffness: 280, damping: 18, mass: 0.3 });11 }, [valueRoundedToPlace]);1213 return (14 <View style={{ height, overflow: 'hidden' }}>15 <View style={StyleSheet.absoluteFill}>16 {Array.from({ length: 10 }).map((_, i) => (17 <Number key={i} mv={animatedValue} number={i} height={height} fontStyle={fontStyle} />18 ))}19 </View>20 <Text style={[styles.text, fontStyle, { opacity: 0 }]}>0</Text>21 </View>22 );23};2425const Number = ({ mv, number, height, fontStyle }) => {26 const translateY = useDerivedValue(() => {27 const placeValue = mv.value % 10;28 const offset = (10 + number - placeValue) % 10;29 let memo = offset * height;30 if (offset > 5) memo -= 10 * height;31 return memo;32 });3334 const animatedStyle = useAnimatedStyle(() => ({ transform: [{ translateY: translateY.value }] }));3536 return (37 <Animated.View style={[styles.numberContainer, animatedStyle]}>38 <Text style={[styles.text, fontStyle]}>{number}</Text>39 </Animated.View>40 );41};4243export const SlidingNumber = ({ value, fontStyle, containerStyle, decimalSeparator = '.', padStart = false }) => {44 const [digitHeight, setDigitHeight] = useState(0);45 const absValue = Math.abs(value);46 const [integerPart, decimalPart] = absValue.toString().split('.');47 const integerValue = parseInt(integerPart, 10);48 const paddedInteger = padStart && integerValue < 10 ? `0${integerPart}` : integerPart;49 const integerDigits = paddedInteger.split('');50 const integerPlaces = integerDigits.map((_, i) => Math.pow(10, integerDigits.length - i - 1));5152 return (53 <View style={[styles.row, containerStyle]}>54 {value < 0 && <Text style={[styles.text, fontStyle]}>-</Text>}55 {digitHeight === 0 && <View style={{ position: 'absolute', opacity: 0 }} onLayout={(e) => setDigitHeight(e.nativeEvent.layout.height)}><Text style={[styles.text, fontStyle]}>0</Text></View>}56 {digitHeight > 0 && (57 <View style={styles.row}>58 {integerDigits.map((_, index) => (59 <Digit key={`pos-${integerPlaces[index]}`} value={integerValue} place={integerPlaces[index]} height={digitHeight} fontStyle={fontStyle} />60 ))}61 {decimalPart && (62 <>63 <Text style={[styles.text, fontStyle]}>{decimalSeparator}</Text>64 {decimalPart.split('').map((_, index) => (65 <Digit key={`decimal-${index}`} value={parseInt(decimalPart, 10)} place={Math.pow(10, decimalPart.length - index - 1)} height={digitHeight} fontStyle={fontStyle} />66 ))}67 </>68 )}69 </View>70 )}71 </View>72 );73};7475const styles = StyleSheet.create({76 row: { flexDirection: 'row', alignItems: 'center' },77 text: { fontSize: 16 },78 numberContainer: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'center', alignItems: 'center' },79});
Props
| Name | Type | Default | Description |
|---|---|---|---|
| value | number | - | The number to display |
| fontStyle | StyleProp<TextStyle> | - | Styles for the digits |
| containerStyle | StyleProp<ViewStyle> | - | Styles for the container |
| decimalSeparator | string | . | Separator char for decimals |
| padStart | boolean | false | Pad integer part with leading zero |
