How to translateX, translateY to a certain coordinate rather than a relative point? - reactjs

My question is about using translateX and translateY in react and/or react-native animations, to animate an object to a certain point.
These two transformations move an object relative to the existing point.
But for the scenario, the existing coordinate is not important and I want to assure the object moves to a certain point wherever it may exist in the screen.
Additional limitation is, I can not animate styles top, bottom, left and right because in react-native, if we animate these styles then we can not use the useNativeDriver={true} directive which causes performance problems.

You can use the onLayout prop to get position (relative to parent) and height/width on any View component.
Something like this should work. You might need to get position of more parent View components, depending on your current structure.
componentDidMount() {
this.animatedValue = new Animated.Value(0);
}
animate() {
const { parentYPosition } = this.state;
Animated.Timing(this.animatedValue, {
toValue: FINAL_POSITION - parentYPosition
}).start();
}
render() {
return (
<View
onLayout={event => {
const { y } = event.nativeEvent.layout;
this.setState({parentYPosition: y})
}}
>
<Animated.View
style={{
position: 'absolute',
top: 0,
transform: [
{
translateY: this.animatedValue
}
/>
/>
);
}
https://facebook.github.io/react-native/docs/view#onlayout

Related

How can I stop my google map reloading every time I try to do something else off the map?

I have React Page. I splitted the page in 2. In the first half I have a form with some fields, and In the second half I have a Google Map Component. I added a Polyline. The path of the polyline gets updated every time the user left clicks (add point) or right clicks (remove point), or if it's dragged. Everything works well until I leave the map and focus on inputs or press some buttons in the other half. Then if I try to add new points or remove them ( this is not working because it has no points to remove) it starts a new polyline.
My methods are very simple, I took them from the docs and they do the job.
const addLatLng = (event) => {
const path = poly.getPath();
path.push(event.latLng);
}
const removeVertex = (vertex) => {
let path = poly.getPath();
path.removeAt(vertex)
}
// eslint-disable-next-line no-undef
google.maps.event.addListener(poly, 'rightclick', function (event) {
if (event.vertex === undefined) {
return;
} else {
removeVertex(event.vertex)
}
})
<div style={{ height: '100vh' }}>
<GoogleMap
center={center}
zoom={3}
onLoad={(map) => setMap(map)}
mapContainerStyle={{ width: '100%', height: '100%' }}
onClick={(event) => { !showInputForLink && addLatLng(event) }}
>
</GoogleMap>
</div>
First methods do the action and this is how I declared the map.
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: ct.MAPS_API_KEY,
libraries: ['drawing, places']
})
// eslint-disable-next-line no-undef
const [map, setMap] = useState(/** #type google.maps.Map */(null))
const poly = new google.maps.Polyline({
strokeColor: '#160BB9',
strokeOpacity: 1.0,
strokeWeight: 3,
editable: true,
});
poly.setMap(map)
And this is how I declared the poly.
I tried to look up in Docs to see if I missed something, but I couldn't find anything that will not lose the focus on the map when I do something else or to start another polyline.
I am not using different components, everything is in on file.
Should I declare a Poly component inside the map? and not use the traditional JavaScript method?
How can I create this without resetting the map when I do actions in the first half?

Why y and x axis parameter didn't work for staggerChildren animation in framer motion?

My question is fairly simple but i'm struggling to find the solution. Here i'm trying to create a staggering animation for each letter in a sentence, i want to use the y axis as a parameter to animate but i'm not getting the result i wanted as the sentence fully mounted without animating. But when i tried to use opacity as a parameter, it works exactly fine. What did i do wrong here? Any help would be appreciated :)
// import "./styles.css";
import { motion } from "framer-motion";
export default function App() {
const quotes = "Hello World.";
const parent = {
animate: {
transition: {
staggerChildren: 0.1,
},
},
};
const child = {
initial: { y: 400 },
animate: {
y: 0,
},
};
return (
<div>
<motion.div variants={parent} initial='initial' animate='animate'>
{
quotes.split('').map((item, index) => (
<motion.span variants={child} key={index}>{item}</motion.span>
))
}
</motion.div>
</div>
);
}
To help you see my problem, here is codesandbox example
https://codesandbox.io/s/busy-mountain-fv58xc?file=/src/App.js:0-620
Animating x and y doesn't work for <span> because it's and inline element. It flows with the content and doesn't have an explicit x and y position to animate.
You can change your spans to a block-level element (like div), or you could add some styling to tell the spans to display as blocks:
<motion.span style={{display: "inline-block"}} variants={child} key={index}>
{item}
</motion.span>

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 native no re-render after updating state array

I have the following code (full example):
import React, { useState, useEffect } from 'react';
import { SafeAreaView, View, Button, StyleSheet, Animated } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
const App = () => {
const [blocks, setBlocks] = useState([]);
const CreateBlockHandler = () => {
let array = blocks;
array.push({
x: new Animated.Value(0),
y: new Animated.Value(0)
});
setBlocks(array);
RenderBlocks();
};
const MoveBlockHandler = (index, event) => {
Animated.spring(blocks[index].x, { toValue: event.nativeEvent.x }).start();
Animated.spring(blocks[index].y, { toValue: event.nativeEvent.y }).start();
};
const RenderBlocks = () => {
return blocks.map((item, index) => {
return (
<PanGestureHandler key={index} onGestureEvent={event => MoveBlockHandler(index,event)}>
<Animated.View style={[styles.block, {
transform: [
{ translateX: item.x },
{ translateY: item.y }
]
}]} />
</PanGestureHandler>
)
});
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.pancontainer}>
<RenderBlocks />
</View>
<Button title="Add block" onPress={CreateBlockHandler} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
},
pancontainer: {
width: '95%',
height:'75%',
borderWidth: 1,
borderColor: 'black'
},
block: {
width: 50,
height: 50,
backgroundColor: 'black'
}
});
export default App;
What does this code do? It's a big square, and a button below it. When I click on the button, a new black square (50x50) is made in the big square. I do this by creating a new array element (the array = blocks). This is done in the function CreateBlockHandler. This does not work correctly!
The function MoveBlockHandler makes the little squares movable. This works!
What does not work? When I create a new black square, the black square is not rendered on the screen. Only when I refresh, the square is rendered. The square is created through CreateBlockHandler, because when I do a console.log(blocks) in that function, I can see that a new array element is added.
How can I force this code to do a full re-render with all the array elements? I tried to wrap the render of the square in a separate function (RenderBlocks) and I'm calling this function every time a new square is made (last line in CreateBlockHandler). The function is called (I can check this with a console.log()) but no squares are rendered.
When you assign blocks to array the reference gete copied which mutates the state, so it doesn't re-render on setState.
const CreateBlockHandler = () => {
let array = [...blocks];
array.push({
x: new Animated.Value(0),
y: new Animated.Value(0)
});
setBlocks(array);
RenderBlocks
There are multiple issues with your code.
As kooskoos pointed out, your state remains referentially equal (it's the same array, only the elements change). This will not trigger re-render.
Also, you are manipulating state of the App component. RenderBlocks component's props and state remain unchanged which implies that they don't need to be re-rendered. Since the component is an anonymous function and is recreated during every render of App, it probably gets re-rendered anyways.
In addition, you are directly calling RenderBlocks, which looks like a component. That is unnecessary and will do nothing here, but if it had any hooks, it would cause problems.
You should probably also conform to the convention that components are PascalCase capitalised and callbacks snakeCase capitalised.

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

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

Resources