I'm using Gosha's Floating Label Input component but the animation is kind of jaggy to animate the fontSize. I tried increasing Animated.timing's toValue but no luck. Which part I need to change to make it smooth? Please advice.
import React, { Component } from 'react';
import { View, TextInput, Animated, Easing } from 'react-native';
export class FloatingLabelInput extends Component {
state = {
isFocused: false,
value: ''
};
componentWillMount() {
this._animatedIsFocused = new Animated.Value(0);
}
handleFocus = () => this.setState({ isFocused: true });
handleBlur = () => this.setState({ isFocused: false });
handleTextChange = (newText) => this.setState({ value: newText });
componentDidUpdate() {
if (this.state.value == "") {
Animated.timing(this._animatedIsFocused, {
toValue: this.state.isFocused ? 1 : 0,
duration: 200,
}).start();
}
}
render() {
const { label, ...props } = this.props;
const labelBoxStyle = {
position: 'absolute',
zIndex: 1,
paddingHorizontal: 5,
backgroundColor: '#fff',
left: 20,
top: this._animatedIsFocused.interpolate({
inputRange: [0, 1],
outputRange: [32, 10],
})
};
const labelStyle = {
fontSize: this._animatedIsFocused.interpolate({
inputRange: [0, 1],
outputRange: [18, 14], // Jaggy animation. Change to [18, 18]
// just to see smooth animation.
}),
color: this._animatedIsFocused.interpolate({
inputRange: [0, 1],
outputRange: ['#aaa', '#3b8c00'],
}),
};
const textInputStyle = {
height: 50,
paddingHorizontal: 20,
fontSize: 18,
color: '#000',
borderWidth: 1,
borderColor: '#3b8c00',
borderRadius: 25
};
return (
<View style={{ paddingTop: 18 }}>
<Animated.View style={labelBoxStyle}>
<Animated.Text style={labelStyle}>{label}</Animated.Text>
</Animated.View>
<TextInput
{...props}
style={textInputStyle}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
value={this.state.value}
onChangeText={this.handleTextChange}
/>
</View>
);
}
}
React Native's Animated unfortunately only supports native animations for some properties, such as opacity and transform. For those, you turn it on by setting useNativeDriver: true. However, trying to use native driver for fontSize results in Style property 'fontSize' is not supported by native animated module.
If you don't set useNativeDriver, or if you set it to false, the animation runs in JavaScript, i.e. every time the underlying animated value changes (in your case, _animatedIsFocused being transitioned from 0 to 1), it sends a message from native through the bridge to JS which then needs to handle it and send a message back to the native thread. That's why it's not smooth.
Afaik there's no way to make it run natively using the built-in in Animated module.
Fortunately, there's a great library called react-native-reanimated that reimplements how animations are run by moving all the animated code to native, and giving you a JavaScript interface to define that code.
For most simple cases, it has an Animated-compatible interface in v1 (note that there's now Reanimated v2 beta which doesn't provide the same interface, at least not yet). You should be able to just change this line of code:
// import { Animated } from 'react-native'
import Animated, { Easing } from 'react-native-reanimated'
And your animation should start running smoothly right away. There's no useNativeDriver to set because all animations run natively.
Note that you probably need to set easing in your timing configuration, e.g.
easing: Easing.inOut(Easing.ease)
You'll also need to make one more change since TextInput is not exported from reanimated. The solution is simple, just create your own animated component:
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
You can animate scale property on text or on parent view (it seems more smoothly).
const labelStyle = {
transform: [
{
scale: this._animatedIsFocused.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.777],
extrapolate: 'clamp',
}),
},
],
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With