React Spring delay switching transitions - reactjs

The title maybe not on spot, but I will try to explain here. I've been using SwitchTransition in in-out mode but since my animation was done using mostly hook useSpring from react-spring package, I would rather move from React Transition Group completely. But I am not quite sure how to achieve the same effect. When a transition happens (for routes) I need for the old component stay visible (say for 2000 milliseconds) while the new one is already visible. Looking at the hook useTransition in react-spring I don't see a way to introduce a delay for leave.
const transition = useTransition(show, null, {
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
config: {
duration: 200, // duration for the whole animation form start to end
},
})
In SwitchTransition it would be something like that:
<SwitchTransition mode='in-out'>
<CSSTransition
key={location.pathname}
timeout={{ enter: 2000, exit: 400 }}
unmountOnExit
onEnter={()=>{callback}}
>
/* here go routes */
</CSSTransition>
</SwitchTransition>
How to do the same in React Spring with useTransition?

Method 1: simplest one is, using trail property
const transition = useTransition(show, null, {
from: { opacity: 0 },
enter: { opacity: 1 },
leave: { opacity: 0 },
trail:150
})
Method 2: For adding different delay to each item by using additional delay array
Here is the working demo for both methods:
https://codesandbox.io/embed/pensive-satoshi-yi4uw?fontsize=14&hidenavigation=1&theme=dark
Hope this helps somebody

I think I dont understand at all, but you can add delay prop
and add a function instead an object like this:
let location = useLocation();
const transition = useTransition(location, {
from: { opacity: 0 },
enter: item => async (next, cancel) => {
await next({ opacity: 1, y: 0, })
await next({ opacity: 1, y: 70, })
},
leave: { opacity: 0, delay:400 },
})
and modify a sequence suitable for you.
i am completely sure you can find another info here:
https://react-spring.io/hooks/use-transition
transition(
(props, item) => {
return (
<a.div className='main' style={props} >
<Switch location={item} >
<Route path='/hours' component={HoursPage} />
<Route path='/city' component={CityPage} />
<Route path='/' component={InicioPage} />
</Switch>
</a.div>
)
}
)

I do not know any separate timing for enter and leave, but you can do something similar with interpolation. For example you define x value instead of opacity.
const transition = useTransition(show, s => (s ? "on" : "off"), {
from: { x: 0 },
enter: { x: 1 },
leave: { x: 2 },
config: {
duration: 4000 // duration for the whole animation form start to end
}
});
In the render method you can interpolate the opacity from x:
{transition.map(({ item, props, key }) => (
<animated.div
key={key}
style={{
opacity: props.x.interpolate({
range: [0.0, 1, 1.25, 2],
output: [0, 1, 0, 0]
})
}}
>
{item ? "on" : "off"}
</animated.div>
))}
So what is going here? Duration is 4 sec. When entering x goes from 0 to 1 it interpolates as opacity also 0 to 1 for the whole 4 sec. When leaving x goes 1 to 2. It interpolates first 1 to 1.25 as opacity 1 to 0 then the opacity remains 0 to the rest of the animation. So leaving animation opacity change from 1 to 0 will happen approximately in 1 sec.
What do you think about it?
Here is an example: https://codesandbox.io/s/zen-curran-nnuiu

Related

Framer Motion different Animations for different properties

I am trying to animate an animation in framer motion using Chakra-ui and Gatsby whereby there is rotation and movement and opacity change.
At the moment the animation works as I intend on the movement on the x axis and rotation using type:spring however the 'bounce effect' also affects the opacity.
I have tried to explicitly define the type:tween for the opacity property, but this has no effect with the opacity of the object still 'bouncing' too. Here is my code:
const Rocketship = ({ top, right, bottom, left, opacity }) => {
const RocketAnim = motion(Box)
const transition = {
default: {
type: 'spring',
damping: 5,
},
opacity: { type: 'tween' },
}
return (
<RocketAnim
layoutId="rocketship"
initial={{ rotate: 25, x: -100, opacity: 0 }}
animate={{ rotate: 45, x: 0, opacity }}
whileHover={{ width: '170px', cursor: 'pointer' }}
transition={transition}
pos="fixed"
width={150}
top={top}
right={right}
bottom={bottom}
left={left}
...
I would be grateful for any advice
Don't have a complete answer because there isn't enough context, but
Your RocketAnim should be defined outside of the component since you don't need it recreated ever render
You don't have to make it a tween, I would keep it as a spring (the default value) and just set bounce to 0
opacity: { bounce: 0}
If you keep it as a tween, I would try adding a duration to trouble shoot the bouncing.

Circle animation not displaying

From:
https://popmotion.io/pose/
I grabbed this code,
const Circle = posed.div({
attention: {
scale: 1.3,
transition: {
type: 'spring',
stiffness: 200,
damping: 0
}
}
})
And I am doing:
<Circle />
But, nothing happens. Am I missing something?
you need to create two states for the posed.div:
const Circle = posed.div({
attention: {
scale: 1.3,
transition: {
type: "spring",
stiffness: 200,
damping: 0
}
},
rest: {
scale: 1
}
});
then you need to pass state to your Circle and some styles to make this component red and circle (popmotion.io doesn't do it for you)
<Circle
className="circle"
pose={isLoading ? "attention" : "rest"}
/>
here is the working solution https://codesandbox.io/s/pose-get-started-zv637?file=/src/index.js
But I am not sure that it is the best way to make loaders because popmotion.io is a tool to animate transitions between states. I can propose you to use pure css loader (https://loading.io/css/)

How to make a div shake from side to side with react spring

I have started playing around with react-spring and I am loving the concept but I am struggling to work out how to build the animation that I want.
I would like to make a div move to the right, then back to start, then to the right, but not as far, back to start, to the right, but not as far again and finally back to start. Just like a spring, that is pulled and when released goes boingoingoing back to it's resting place.
I can see from the documentation how to adjust the feel of the spring and how to trigger the animation, but I have never made an animation before so knowing which properties to change and how to make it loop properly are what I am looking for help on.
Edit: I have this animation so far, and it works, but it feels very disjointed.
const shake = useSpring({
from: { "margin-left": 0 },
to: [
{ "margin-left": 30 },
{ "margin-left": 0 },
{ "margin-left": 20 },
{ "margin-left": 0 },
{ "margin-left": 10 },
{ "margin-left": 0 }
],
config: {
mass: 1,
tension: 500,
friction: 10
}
});
Currently it is clearly three movements, can I decrease the delay between the movements so that it looks like one movement?
Is margin left the best CSS property to use?
UPDATE START
I just came across a simple solution for this problem. It I way better than the one I tried to achieve with configuration. It uses interpolation with range. It is using transform now but can easily adopted to margin.
export default function App() {
const { x } = useSpring({
from: { x: 0 },
to: { x: 1 }
});
return (
<div className="App">
<animated.div
style={{
transform: x
.interpolate({
range: [0, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
output: [180, 220, 180, 220, 180, 220, 180, 200]
})
.interpolate(x => `translate3d(${x}px, 0px, 0px)`)
}}
>
Shake
</animated.div>
</div>
);
}
https://codesandbox.io/s/awesome-framework-go5oh?file=/src/App.js:103-609
UPDATE END*
You can achieve the bouncy effect. There are 3 variable controlling the spring based animation:
mass
friction
tension
You can play with the pink square here: https://www.react-spring.io/docs/hooks/api
I recommend low mass and friction and high tension. You can set these variable in every animation type. For example:
useSpring({ ..., config: {mass: 1, tension: 500, friction: 10} });

Problems with parallax header in react native

Trying to write a parallax scroll view in react native. First off, this is what I have so far:
The only problem, as you can see in the GIF above, is that, children in scroll view disappear at the red line, which is the ScrollView's original top border position. I've tried to change the top border position but it doesn't work, continue to read. The height of the parallax header is 170px, after 100px scrolled, the image stops going up, therefore, the sticky header height is 70px
Here is the code for the GIF above:
const parallaxHeaderHeight = 170;
const headerHeight = 70;
const headerDiff = parallaxHeaderHeight - headerHeight; // 100px
class ParallaxScrollView extends Component {
constructor(props) {
super(props);
this.scrollY = new Animated.Value(0); // How many pixels scrolled
}
<View style={{ flex: 1 }}>
<Animated.Image
source={{ uri: '...' }}
style={{
width: ..., height: ...,
transform: [
{
translateY: this.scrollY.interpolate({
inputRange: [-1, 0, headerDiff, headerDiff + 1],
outputRange: [0, 0, -headerDiff, -headerDiff]
})
},
{
scale: this.scrollY.interpolate({
inputRange: [-1, 0, 1],
outputRange: [1.005, 1, 1]
})
}
]
}}
/>
<Animated.ScrollView
scrollEventThrottle={1}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.scrollY } } }],
{ useNativeDriver: true }
)}
>
// Then, render children here
</Animated.ScrollView>
</View>
}
Then, I've tried to transform the top border of scroll view, but this happens:
Look at the first child of the scroll view, 0, it disappears when I've scrolled 100px, but what I want is for it to stay viewable when scrolling the first 100px. I know why this is happening, but I can't find a solution. How should I modify my code?
Answering my own question: This problem can be solved with a 'hacky' solution, but is not recommended, for reasons listed below.
First of all, the solution is - Add an initial padding to the scroll view's children (Looking at the code snippet in the question and adding this part to it):
...
<Animated.Image
...
style={{
...
position: 'absolute', zIndex: 1,
top: 0, left: 0, right: 0,
height: parallaxHeaderHeight // which is 170px in my case
...
}}
...
/>
<Animated.ScrollView
...
contentContainerStyle={{ paddingTop: parallaxHeaderHeight }}
...
>
...
</Animated.ScrollView>
...
This gives me:
The flaw is that, part of the scroll bar is hidden behind the image header due to the fact that the header has position = absolute and zIndex = 1. But if the scroll bar is not important, then never mind, this 'hacky' solution is just fine and doesn't cause any performance issue

Having trouble with react-grid-layout example

Been trying to get into react and was looking at react-grid-layout when I came across a bit of a roadblock. I've pasted in the example from here essentially as is, but for some reason, when I drag an element it's not sticking. The error I'm getting in the console is:
Uncaught TypeError: this.props.onLayoutChange is not a function
I'm sure it's a simple thing that I'm missing, but this is my first React project and I would appreciate some guidance.
My code is included below:
'use strict';
var React = require('react');
var _ = require('lodash');
var ResponsiveReactGridLayout = require('react-grid-layout').Responsive;
/**
* This layout demonstrates how to use a grid with a dynamic number of elements.
*/
var AddRemoveLayout = React.createClass({
getDefaultProps() {
return {
className: "layout",
cols: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
rowHeight: 100
};
},
getInitialState() {
return {
items: [0, 1, 2, 3, 4].map(function(i, key, list) {
return {i: i, x: i * 2, y: 0, w: 2, h: 2, add: i === list.length - 1};
}),
newCounter: 0
};
},
createElement(el) {
var removeStyle = {
position: 'absolute',
right: '2px',
top: 0,
cursor: 'pointer'
};
var i = el.add ? '+' : el.i;
return (
<div key={i} _grid={el}>
{el.add ?
<span className="add text" onClick={this.onAddItem} title="You can add an item by clicking here, too.">Add +</span>
: <span className="text">{i}</span>}
<span className="remove" style={removeStyle} onClick={this.onRemoveItem.bind(this, i)}>x</span>
</div>
);
},
onAddItem() {
console.log('adding', 'n' + this.state.newCounter);
this.setState({
// Add a new item. It must have a unique key!
items: this.state.items.concat({
i: 'n' + this.state.newCounter,
x: this.state.items.length * 2 % (this.state.cols || 12),
y: Infinity, // puts it at the bottom
w: 2,
h: 2
}),
// Increment the counter to ensure key is always unique.
newCounter: this.state.newCounter + 1
});
},
// We're using the cols coming back from this to calculate where to add new items.
onBreakpointChange(breakpoint, cols) {
this.setState({
breakpoint: breakpoint,
cols: cols
});
},
onLayoutChange(layout) {
this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
onRemoveItem(i) {
console.log('removing', i);
this.setState({items: _.reject(this.state.items, {i: i})});
},
render() {
return (
<div>
<button onClick={this.onAddItem}>Add Item</button>
<ResponsiveReactGridLayout onLayoutChange={this.onLayoutChange} onBreakpointChange={this.onBreakpointChange}
{...this.props}>
{_.map(this.state.items, this.createElement)}
</ResponsiveReactGridLayout>
</div>
);
}
});
module.exports = AddRemoveLayout;
React.render(<AddRemoveLayout/>, document.getElementById('app'))
The error you are receiving is an error about a missing prop. In a react component you basically have 2 places to keep your data, in its parent and in your component itself. Your parent often has props while declaring it because those are properties you pass to the child (like an attribute in an HTML tag). Then we have the state which is data inside a component itself.
The error you are receiving is saying that we didn't get a required prop from our parent (You can also see that inside the onLayoutChange(layout) function a call is being made to the this.props.onLayoutChange(layout) method).
So basically we are missing a few props. In the example from GitHub there is a root file called test-hook.jsx (https://github.com/STRML/react-grid-layout/blob/master/test/test-hook.jsx). This root node has as a child ( the code you are trying to render directly ) in which it is passing the required function as a property.
You can either use the test-hook.jsx or you can write your own root node which has a state with the layout and the required function which updates that state (see the github example on how to do that).
So after some searching, I figured out that the example was specifying the onLayoutChange function as a placeholder. If I wanted a custom funcion, I needed to define that.
Simply removing this function altogether and using the default fixed the issue.
Remove this:
onLayoutChange(layout) {
this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
#Dirk-Jan explained it well. But the proper solution IMHO is to remove the prop call:
onLayoutChange(layout) {
// this.props.onLayoutChange(layout);
this.setState({layout: layout});
},
So the meaningful part is still there. In the examples the test-hook.jsx parent has to get hold of the layout so it can display it outside of the layout container for demonstration purposes. In a real-world application we don't need that.

Resources