I tried to create a text input with floating placeholder on it, changing background, shadows etc. Input has 3 states - when it's empty, when it's not empty and when it's focused.
You can see how these inputs should work on my website (works only on desktops): https://www.treemotion.org/sign-in
Here you can see, how it's working now on my phone: https://www.icloud.com/photos/#0lqAi71mJLyev50Bdz_BmEmfA
Everything is smooth and works well, but when I type something or delete it and blur the input, animation doesn't work - it just "switches" between two states of animation.
TextField.tsx:
import React, { useState } from 'react'import { TextInput, Animated, StyleSheet, View } from 'react-native'const styles = StyleSheet.create({ TextFieldWrapper: { borderRadius: 4, position: 'relative', marginTop: 20, width: 280, shadowOffset: { width: 0, height: 0}, shadowColor: '#000000', shadowRadius: 10, elevation: 5, }, TextField: { width: 280, paddingTop: 30, paddingLeft: 10, paddingRight: 10, paddingBottom: 10, fontSize: 18, fontWeight: 'bold', borderRadius: 4 }, TextFieldLabel: { position: 'absolute', left: 10 } })const TextField: React.FC<{ secureText: boolean, placeholder: string }> = ({secureText, placeholder}) => { const [value, setValue] = useState<string>('') const inputFocusAnimation = new Animated.Value(0) const inputContentAnimation = new Animated.Value(0) const emailLabelMarginTop = inputContentAnimation.interpolate({ inputRange: [0, 1], outputRange: [20, 10] }) const emailLabelFontSize = inputContentAnimation.interpolate({ inputRange: [0, 1], outputRange: [18, 14] }) const textFieldBackground = inputFocusAnimation.interpolate({ inputRange: [0, 1], outputRange: ['rgba(242,242,242,1)', 'rgba(255,255,255,1)'] }) const textFieldShadow = inputFocusAnimation.interpolate({ inputRange: [0, 1], outputRange: [0, .2] }) function handleFocus() { Animated.timing( inputFocusAnimation, { toValue: 1, duration: 300 } ).start() if(!value) { Animated.timing( inputContentAnimation, { toValue: 1, duration: 300 } ).start() } } function handleBlur() { Animated.timing( inputFocusAnimation, { toValue: 0, duration: 300 } ).start() if(!value) { Animated.timing( inputContentAnimation, { toValue: 0, duration: 300 } ).start() } } return (<Animated.View style={{...styles.TextFieldWrapper, backgroundColor: textFieldBackground, shadowOpacity: textFieldShadow}}><TextInput secureTextEntry={secureText} style={styles.TextField} placeholder='' onFocus={() => handleFocus()} onBlur={() => handleBlur()} onChangeText={(e: string) => setValue(e)}></TextInput><Animated.Text style={{...styles.TextFieldLabel, top: emailLabelMarginTop, fontSize: emailLabelFontSize}}>{placeholder}</Animated.Text></Animated.View> )}export default TextField
I've also tried to "manage" these animations inside useEffect like this:
useEffect(() => { ... }, [focus, value])
But I couldn't make it work well, so I decided to stay with first option. Any ideas?