How to scale the size of a button when the user holds down in Animated React / React Native? - reactjs

I'm trying to create some code that increases the size of a button when the user holds it down then reverts back to the initial size when released.
I'm relatively new to React/RN and have searched tons of websites to find the result, but can't seem to find anything.
I can't tell whether I should be using PanResponder here or not. I also tried using Animated.timing, but the timing is hard-coded & not bound to the length of time that the user holds down the button. I tried Animated.spring, but again that's not bound to length of time that the user holds the button down.
I'll post a quick gif that replicates what I'm trying to go for.
https://imgur.com/a56pSQl
Here's what I have so far:
this.scaleAnimation = new Animated.value(3)
handlePress = () => {
Animated.spring(this.scaleAnimation, {
toValue: 4,
friction: 2,
tension: 160
}).start()
}
render() {
const pauseStyle = {
transform: [
{ scale: this.scaleAnimation }
]
}
return (
<TouchableWithoutFeedback onPress={this.handlePress}>
<Animated.View style={[ pauseStyle ]}>
<Ionicons name="md-pause" />
</Animated.View>
</TouchableWithoutFeedback>
)
}
Any takes are greatly appreciated :D

Please find the detailed answer with code snippets.
Add this to constructor of component
this.handlePressIn = this.handlePressIn.bind(this);
this.handlePressOut = this.handlePressOut.bind(this);
this.animatedValue = new Animated.Value(1);
This method is for scaling button with animation when button gets pressed
handlePressIn() {
Animated.spring(this.animatedValue, {
toValue: 3,
friction: 3,
tension: 40
}).start();
}
This method is for resetting button to its initial scale with animation when touch gets released
handlePressOut() {
Animated.spring(this.animatedValue, {
toValue: 1,
friction: 3,
tension: 40
}).start();
}
Render method
render() {
const animatedStyle = {
transform: [{ scale: this.animatedValue }]
}
return (
<TouchableWithoutFeedback
onPressIn={this.handlePressIn}
onPressOut={this.handlePressOut}
>
<Animated.View style={animatedStyle}>
<Text style={styles.text}>Press Me</Text>
</Animated.View>
</TouchableWithoutFeedback>
);
}
You can use different types of animations. Please find the link for reference:
https://facebook.github.io/react-native/docs/animated#configuring-animations

Related

How to apply animation in react-native?

I want to add animation to this search bar.
when the user touches the search bar it size decreases and again increases and gets to its default size(like animation in popups)
This is my code
<View style={{flexDirection:'row',alignSelf:'center'}}>
<TextInput
onChangeText={(text) => setSearch(text)}
onFocus={()=>{
setSize('92%');
setInterval(()=>{setSize('95%')},1000)
}}
placeholder="Search"
style={{...styles.searchbox,width:size}}
></TextInput>
</View>
I am currently trying to change width..
Firstly, I suggest you to take a look at RN animated documentation, maybe it will help you to understand better how the animations work.
Also, it depends on what you're having there: a class component or a function component.
If you're using a function component, you could do it like this:
Creating a custom hook, called, let's say useAnimation(), which would look something like this
export const useAnimation = ({ doAnimation, duration, initialValue, finalValue }) => {
const [animation, setAnimation] = useState(new Animated.Value(initialValue))
useEffect(() => {
Animated.spring(animation, {
toValue: doAnimation ? initialValue : finalValue,
duration,
bounciness: 8,
useNativeDriver: false
}).start();
}, [doAnimation]);
return animation
}
As it is said in the documentation, you could animate only Animated components, and for example if you want to have an animated View, the tag will be <Animated.View> {...} </Animated.View, but for the <TextInput> we have to create the animated component:
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
and combining the first 2 steps
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
const [collapsed, setCollapsed] = useState(true)
const animation = useAnimation({ doAnimation: collapsed, duration: 300, initialValue: 20, finalValue: 200 });
const onFocusText = () => setWidth(false)
return (
<AnimatedTextInput
onFocus={onFocusText}
placeholder={"Search something"}
style={{width: animation, height: 50, borderColor: 'gray', borderWidth: 1, borderRadius: 4, padding: 10}}
/>
)
Also, if you're having a class component, you could have a method which will start the animation (but don't forget about the step 2 which is essential)
private size: Animated.Value = new Animated.Value(COLLAPSED_VALUE)
get resizeInputWidth(): Animated.CompositeAnimation {
return Animated.timing(this.size, {
toValue: EXPANDED_VALUE,
duration: 500,
})
}
startAnimation = () => this.resizeInputWidth.start()
<AnimatedTextInput
onFocus={this.startAnimation}
style={{ width: this.size }}
/>

How to make a Header that Animates from Transparent to Opaque Color on Scrolling down in React-Native React Navigation 5?

I'm trying to make header that will animate from transparent to solid opaque color upon scrolling down using in React-Native React Navigation 5.
Starts to transition to opaque when scrolling halfway
Becomes fully opaque when reach the maximum offset
You can do this by setting the header style opacity to an animated value.
First define your animated value, we'll interpolate the yOffset to get the opacity desired.
const yOffset = useRef(new Animated.Value(0)).current;
const headerOpacity = yOffset.interpolate({
inputRange: [0, 200],
outputRange: [0, 1],
extrapolate: "clamp",
});
then you want to attach an animated.event listener to an animated scroll view
<Animated.ScrollView
onScroll={Animated.event(
[
{
nativeEvent: {
contentOffset: {
y: yOffset,
},
},
},
],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
>
Your content should be inside the scroll view
In your screen add a on mount or use effect where you set the animatedValue as the header opacity
useEffect(() => {
navigation.setOptions({
headerStyle: {
opacity: headerOpacity,
},
headerBackground: () => (
<Animated.View
style={{
backgroundColor: "white",
...StyleSheet.absoluteFillObject,
opacity: headerOpacity,
}}
/>
),
headerTransparent: true,
});
}, [headerOpacity, navigation]);
I've used header transparent and header background so that the background component changes also.
Here is an example:
https://snack.expo.io/#dannyhw/react-navigation-animated-header
const handleScroll = () => {
YourElementRef.current.style.backgroundColor = `rgba(245, 245, 245, ${window.scrollY > 300 ? 1 : '0.' + (window.scrollY * 3)})`;
}
window.addEventListener('scroll', handleScroll, true);
You need to add scroll listener and call function that animates it.
The scroll element is represented by a ref stuff. e.g.
const YourElementRef = React.useRef(null);
<SomeElement ref={YourElementRef}...

React-three-fiber with react-spring animations

sorry if this seems like an obvious answer, but I am trying to create an animation similar to this. I am trying to create one similar with react-three-fiber but I can't use the ones shown in the docs since they manipulate the style transform property which isn't possible with three.js. I have tried to manipulate it through the position attribute and props but it comes with an error as shown here and I don't really know where to start to fix this. Here is my code:
const props = useSpring({
to: async next => {
await next({ position: [100, 100, 100] });
await next({ position: [50, 50, 50] });
},
from: { position: [100, 100, 100] },
config: { duration: 3500 },
reset: true
});
return (
<Canvas>
<a.group {...props}>
<Box position={[-1.2, 0, 0]} />
<Box position={[1.2, 0, 0]} />
</a.group>
</Canvas>
)
I have used react-spring successfully with hovering and scale but it just doesn't work when using position.
i think something like that is best done with trigonometry
const ref = useRef()
useFrame(() => {
ref.current.position.y = Math.sin(state.clock.getElapsedTime()) * 10
})
return <group ref={ref}> ...
Math.sin will yield a value between -1 to 1 and alternates smoothly between the two. multiply your factor (the distance) and you have it. here's an example that dollies the camera like that: https://codesandbox.io/s/r3f-lod-e9vpx
otherwise it's react-spring/three, not react-spring: https://codesandbox.io/s/clever-bartik-tkql8

Pull Scrollview to reveal View - React Native

I'm trying to build something similar to IMessage's and WhatsApp's header in react native, where users can pull down to reveal a search bar in the header.
I have been able to pull down to reveal a hidden input, but because the scrollview's y value becomes negative on pull, it will bounce back to y = 0 and prevent the input from sticking to the top. I have tried using both translateY and scaleY to reveal the hidden input.
class List extends Component {
scrollY = new Animated.Value(0)
render() {
const translateY = this.props.scrollY.interpolate({
inputRange: [ -50, 0 ],
outputRange: [ 50, 0 ],
extrapolate: 'clamp',
})
return (
<>
<Animated.View style={[
styles.container,
{ transform: [ { translateY } ] },
]}>
<Input />
</Animated.View>
<Animated.ScrollView
onScroll={Animated.event(
[ { nativeEvent: { contentOffset: { y: this.scrollY } } } ],
{ useNativeDriver: true }
)}
scrollEventThrottle={16}
>
{...}
</Animated.ScrollView>
</>
)
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: colors.white,
width: windowWidth,
height: 50,
position: 'absolute',
top: -50,
zIndex: -99,
},
});
I found this Stack Overflow post that has been useful to reference but it is IOS specific Pull down to show view
I solved this by using contentOffset and without any animations. I needed to make sure the scrollview was at least the size of the phone's windowHeight and then used contentOffset to push the initial y value of the Scrollview to the size of the header
<ScrollView
ListHeaderComponent={() => (
<Header headerHeight={hiddenHeaderHeight} />
)}
contentContainerStyle={{ minHeight: windowHeight }}
contentOffset={{ y: hiddenHeaderHeight }}
...
This solution works for a Flatlist as well.
One thing to note is contentOffset is an ios specific prop
check out this medium article. It provides a detailed explanation of how to do something similar to your desired behavior.

error: Attempted to assign to read only property on using Animated react native

For some reason I'm not able to see what I'm doing wrong with my code. I seem to be using Animated just as the documentation shows but this error keeps coming.
The code snippet:
import React, {
Component
} from 'react';
import {
StyleSheet,
Image,
Animated,
} from 'react-native'
import Header from './../components/Header'
export default class DrawerShell extends Component {
constructor(props) {
super(props)
this.state = {
showNav: false,
}
this.anim = new Animated.Value(0)
this.openDrawer = this.openDrawer.bind(this)
}
openDrawer() {
let toValue
this.setState({
showNav: !this.state.showNav
}) !this.state.showNav ? toValue = 1 : toValue = 0
Animated.timing( // Animate value over time
this.anim, // The value to drive
{
toValue: 1, // Animate to final value of 1
duration: 300,
}
).start()
}
render() {
let {
showNav
} = this.state
return ( <
Animated.View style = {
[
styles.appContainer,
{
transform: [{
translate: [
this.anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 200],
}),
this.anim.interpolate({
inputRange: [0, 1],
outputRange: [0, 80],
}),
0
]
},
{
scale: this.anim.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.7]
})
}
]
},
]
} >
<
Image source = {
{
uri: "splash_bg"
}
}
style = {
styles.bgImage
} >
<
Header title = "hi there"
onPress = {
this.openDrawer
}
/> <
/Image>
</Animated.View>
);
}
}
Might be useful for others coming from Google. Be sure you're using animated values within animated components like Animated.View. Often overlooked when 'upgrading' a view to be animated.
Figured. Two reasons why this error was being thrown.
I was interpolating the same value multiple times. Which is not allowed.
Setting state would call interpolate once more. Which was not required.
Once I Stopped doing interpolate multiple times on the same value and made it independent of state, the error went away.
For those who come here, you need to have animated values inside <Animated.View>!
Refer to this issue:
https://github.com/facebook/react-native/issues/10716#issuecomment-258098396
i think this might work you assign value directly to the state object in returnary operator and that case the error
openDrawer() {
let toValue
this.setState({
showNav: !this.state.showNav
})
toValue = (!this.state.showNav) ? 1 : 0 // change this this line case error
Animated.timing( // Animate value over time
this.anim, // The value to drive
{
toValue: 1, // Animate to final value of 1
duration: 300,
}
).start()
}
In my case I found the animated transform: [{ scale: ...}] value needs to be applied through a style property rather than directly to the view.
This is the working, not-animated code I started from:
<View transform={[{ scale: 0.8 }]}>
...
</View>
But this throws the attempted to assign to read-only property exception:
const animVal = useRef(new Animated.Value(0.8)).current
<Animated.View transform: { scale: animVal }>
...
</Animated.View>
This works!:
const animVal = useRef(new Animated.Value(0.8)).current
<Animated.View style={{ transform: [{ scale: animVal }]}}>
...
</Animated.View>
(This is not exactly the problem the question holder had but it may help others who stumble upon this through Google)

Resources