It should be really easy to animate between routes when the URL changes, preferably using ReactCSSTransitionGroup
.
@mjackson do you have any examples of doing this anywhere by chance?
@cavneb We're still working on this. I have a half-finished example, but I couldn't get it working.
I would also love this feature!
I was able to achieve this without issue with the React TransitionGroup (I'm using JS-based animations) by simply doing:
<TransitionGroup>
<this.props.activeRouteHandler />
</TransitionGroup>
woah ... an unexpected side-effect of #90, @jbrantly would love for you to add a demo to examples/
:D
This actually would have worked before #90 I think, just put {this.props.activeRoute}
inside the transition group.
+1 for the example mention :)
Sorry, I started to do this but didn't have the time to finish. Glad to see you got it done anyways!
Seems like wrapping <this.props.activeRouteHandler />
in <TransitionGroup>
no longer works in version 0.6.x (and 0.7.x) unless you add addHandlerKey={true}
to your Route.
Does anyone else have a better solution to this issue? Would be sad to lose out on the performance boost you get from not applying addHandlerKey={true}
There are necessarily two elements, otherwise the animation isn't possible. You need keys. The DOM diff just updates the changed parts of a node tree. If that's all you're doing, there is no previous element to animate out. Make sense?
Yeah, makes sense. Thanks :)
Would be awesome to see an example of react-router and transitions. :-)
Well, my good sir; I am embarrassed. Thanks!
@iammerrick np! :)
@iammerrick there is also this one :P http://jsbin.com/pamon/2
Thanks! :-)
On Mon, Oct 27, 2014 at 4:18 PM Ryan Florence [email protected]
wrote:
@iammerrick https://github.com/iammerrick there is also this one :P
http://jsbin.com/pamon/2—
Reply to this email directly or view it on GitHub
https://github.com/rackt/react-router/issues/17#issuecomment-60679437.
so this won't work with animations between pages that are handled by the same handler?/users/john
and /users/mike
or do I just need to make sure the john
and mike
part are in the keys?
I tried building a version of the example using TransitionGroup's lifecycle hooks rather than CSSTransition and the hooks don't seem to get called. Am I missing something, or is this behavior only supposed to work with CSSTransitionGroup at the moment?
Is there a way or a pattern to make this more flexible? I don't necessarily want everything under a RouteHandler
to transition.
Any guidance on this?
I'm interested in this too, current example seems outdated #1194
working on this in the 1.0 branch, there's no more RouteHandler stuff going
on, its all just props (props.children usually) and so animating between
routes should be identical to any other component that's animating
children. there will be much better documentation/examples for animation
too. thanks for your patience :)
On Tue, May 19, 2015 at 8:45 PM, Daniel Belohlavek <[email protected]
wrote:
I'm interested in this too, current example seems outdated #1194
https://github.com/rackt/react-router/issues/1194—
Reply to this email directly or view it on GitHub
https://github.com/rackt/react-router/issues/17#issuecomment-103733846.
I created a pen with a working example:
http://codepen.io/luisrudge/pen/QbEeOR
Hey @luisrudge thanks for your answer! I got _that_ far myself but I got several warnings and an error when trying to run the code :(
Warning: Failed Context Types: Required context `routeDepth` was not specified in `RouteHandler`.
Warning: Failed Context Types: Required context `router` was not specified in `RouteHandler`.
Warning: owner-based and parent-based contexts differ (values: `undefined` vs `1`) for key (routeDepth) while mounting RouteHandler (see: http://fb.me/react-context-by-parent)
Warning: owner-based and parent-based contexts differ (values: `undefined` vs `function (props, context) [...long code...]
Uncaught TypeError: Cannot read property 'getRouteAtDepth' of undefined
I read somewhere that might have to do with having duplicate React instances. I'm using Browserify!
Never saw that. I'm using webpack, but this pen also doesn't throw this errors
Enviado pelo meu Windows Phone
De: Daniel Belohlavekmailto:[email protected]
Enviada em: ‎20/‎05/‎2015 00:27
Para: rackt/react-routermailto:[email protected]
Cc: LuÃs Rudgemailto:[email protected]
Assunto: Re: [react-router] Animated transitions (#17)
Hey @luisrudge thanks for your answer! I got _that_ far myself but I got several warnings and an error when trying to run the code :(
Warning: Failed Context Types: Required context `routeDepth` was not specified in `RouteHandler`.
Warning: Failed Context Types: Required context `router` was not specified in `RouteHandler`.
Warning: owner-based and parent-based contexts differ (values: `undefined` vs `1`) for key (routeDepth) while mounting RouteHandler (see: http://fb.me/react-context-by-parent)
Warning: owner-based and parent-based contexts differ (values: `undefined` vs `function (props, context) [...long code...]
Uncaught TypeError: Cannot read property 'getRouteAtDepth' of undefined
I read somewhere that might have to do with having duplicate React instances. I'm using Browserify!
Reply to this email directly or view it on GitHub:
https://github.com/rackt/react-router/issues/17#issuecomment-103743882
How is the progress with animations in 1.0
? :)
I want to create that transition.
How can i do it with [email protected], [email protected] ?
Check out the oficial example. There are alternatives to react-addons-css-transition-group such as velocity-react and react-motion.
Here's a simple example of a RouteTransition
component, that will animate all it's children:
import React from 'react';
import {TransitionMotion, spring} from 'react-motion';
const willEnter = children => ({children, opacity: spring(0), translate: spring(999)});
const willLeave = (key, {children}) => ({children, opacity: spring(0), translate: spring(-999)});
const getStyles = (children, pathname) => ({[pathname]: {children, opacity: spring(1), translate: spring(0)}});
export default function RouteTransition({children, pathname}) {
return (
<TransitionMotion
styles={getStyles(children, pathname)}
willEnter={willEnter}
willLeave={willLeave}
>
{interpolated =>
<div>
{Object.keys(interpolated).map(key =>
<div
key={`${key}-transition`}
style={{
height: '100%',
width: '100%',
position: 'absolute',
opacity: interpolated[key].opacity,
transform: `translateX(${interpolated[key].translate}px)`
}}
>
{interpolated[key].children}
</div>
)}
</div>
}
</TransitionMotion>
);
}
Simply wrap and it's done:
<RouteTransition pathname={this.props.location.pathname} style={{visibility: 'hidden', width: '100%', height: '100%'}}>
{this.props.children}
</RouteTransition>
If like me you're just trying to get simple CSS transitions to work (especially fade-out) with react-router 2.0, and you don't need the heavy artillery, you can use "react-easy-transition", a very lightweight module that I wrote to solve that.
<EasyTransition
path={location.pathname}
initialStyle={{opacity: 0}}
transition="opacity 0.3s ease-in"
finalStyle={{opacity: 1}}
>
{this.props.children}
</EasyTransition>
The demo with redux integration
@misterfresh huge thanks!
@misterfresh many thanks!
thanks!!!!
None of these examples work with asynchronous routing, has anyone got any examples of that?
Looking for something that starts fading out before the next chunk has loaded.
Has anyone been able to crossfade with React Router? Every example I've seen will not fade in the new route until the old route is fully faded out, which flashes the screen in a jarring way. Looking for a crossfade solution!
The bundled animation example shows that.
Following the canonical example for animation I noticed that the _leave animation_ is called when the new route could be resolved (e.g. when onEnter
is async and finished its work by calling the callback
). Say I switch from route-a
to route-b
and route-b
fetches resources, which this takes 4 seconds nothing will be animated in these 4 seconds. The onLeave
of route-a
is called immediately however.
For me this is very surprising. Is there any way to trigger the _leave animation_ for route-a
immediately just like onLeave
is called immediately for route-a
?
I managed it in the end. Although for my use case I had a swiping transition. Things I learnt:
ReactTransitionGroup
or ReactCSSTransitionGroup
, these animations aren't interruptible, makes it feel clunky if the user navigates quickly. The componentWillEnter
won't fire until it's componentWillLeave
method has finished completing if the user navigates forward and then back to the same component (means they have to wait for the fade to complete)getComponent
in react-router, seems like this isn't interruptible. I.e. if I navigate to a new page which calls getComponent
and then the user presses back before getComponent
resolves, they will end up with the wrong component loaded for the current url when getComponent
does resolve. Didn't test this to much so could be wrong.ReactTransitionGroup
the lifecycle methods won't get called if you're using any sort of higher order component like react-redux's connect, which is a pretty common use case. For react-redux explicitly you can use their {withRef: true}
parameter and then use getWrappedInstance
, although it's the only escape hatch I know, I strongly dislike it, as it seems to break my seperation of concerns between Dumb and State components.I went with react-motion, it's the only library that seems to exist that allows you to interrupt animations, even native css animations can't do it, seems animation on the web is seriously lacking, happy to be told I'm wrong.
I have an App component that renders all the child components (pages) within it. I added some logic (using redux) that would unmount the current page when a script chunk was loading, this would give me my page swipe out animation before the next chunk has started loading.
Then my navigation code looked something like this, i.e. the user would click a 'Next' button and it would fire the below code.
dispatch({
type: 'UPDATE_SCRIPT_CHUNK_IS_LOADING',
value: true,
})
// Webpack loading of the next chunk needed to display the next page
require.ensure([], (require) => {
this.context.router.push(`Personal/Index`)
dispatch({
type: 'UPDATE_SCRIPT_CHUNK_IS_LOADING',
value: false,
})
}, "personal")
The dispatch updates my app redux state so that my App component knows to unmount it's current page. When the next chunk has loaded 'script chunk loading' is turned off in state and the next page is animated in. react-motion takes care of the animating quite elegantly, although it took me a fair bit of time figuring out how their <TransitionMotion />
api worked.
The downside to this method, is the delay the user gets between clicking to navigate and the url in the address bar changing (although the unmount animation is instant). And having to wrap navigation points in chunk loading statements. I think this is the best we can do at the moment though.
@AlastairTaft That's gold! Especially the parts about what is interruptible and what not. Thank you. I also tried react-motion
today and struggled with its API. Do you have an example for that, too?
I created a PanelsTransition
component that would animate a panel in and out when the user navigates.
import React, { Component } from 'react'
import { TransitionMotion, spring, presets } from 'react-motion'
// These are just some convenience functions to help calculate the left position of the
// animation
import { getPanelWidth, calculatePanelToEdgeOfScreenDistance } from './../../Panel'
// The css just makes the leaving component `position:absolute` so that it doesn't
// take up space in the document
require('./PanelsTransition.css')
const calculatePosition = (x, delta) => {
// If we're not in a browser environment don't do anything
if (typeof window === 'undefined') return 0;
const dist = calculatePanelToEdgeOfScreenDistance()
return dist * x * delta
};
class PanelsTransition extends Component {
static propTypes = {
/**
* The direction of navigation, 1 for forwards, -1 for backwards
*/
delta: React.PropTypes.number,
/**
* Set this to true to animate on initial render
*/
animateOnInitialRender: React.PropTypes.bool,
};
static defaultProps = {
animateOnInitialRender: false,
};
willLeave = () => {
return {
transitionAmount: spring(-1, {stiffness: 300, damping: 26}),
finalAmount: -1,
}
};
willEnter = () => {
return {
transitionAmount: 1,
finalAmount: 1,
}
};
render = () => {
const { pathname, delta, animateOnInitialRender } = this.props
// If it isn't an initial render then animate in the first component
if (animateOnInitialRender){
var defaultStyles = [{
key: pathname,
style: {
transitionAmount: spring(1, presets.gentle),
finalAmount: 1,
},
data: {
component: this.props.children,
},
}]
}
return <TransitionMotion
willLeave={this.willLeave}
willEnter={this.willEnter}
defaultStyles={defaultStyles}
styles={[{
key: pathname,
style: {
transitionAmount: spring(0, presets.gentle),
finalAmount: 0,
},
data: {
component: this.props.children,
},
}]}>
{interpolatedStyles => {
return <div>
{interpolatedStyles.map(config => {
const isLeaving = config.style.finalAmount == -1
return <div
// Makes leaving components absolute so they don't take up space
// in the document
className={`PanelsTransition${isLeaving ? '-leaving' : ''}`}
key={config.key}
>
<div style={{
position: 'relative',
left: calculatePosition(config.style.transitionAmount, delta),
}}>
{config.data.component}
</div>
</div>
})}
</div>
}}
</TransitionMotion>
}
}
export default PanelsTransition
In my App component I then had something like this
class App extends Component {
render = () => {
const { scriptChunkIsLoading } = this.props
// I've got my routing state in redux and can figure out
// the direction of navigation there but this can come from
// anywhere or be passed in as props
const state = this.context.store.getState()
const { delta, initialRender } = state.routing
var pathname = this.props.location.pathname
// Ensure the path always starts with a slash, I've seen it be inconsistent
// sometimes
if (pathname.startsWith('/') == false)
pathname = '/' + pathname
// Make sure our child element has a key that relates to our page
// this is important
var child = React.cloneElement(this.props.children, {
key: pathname,
})
if (scriptChunkIsLoading){
// This is kind of crude, it's just means we override our child element and
// render an empty div that takes up space in the document,
// helps with the unmount animation. Only useful for me as a way of pushing
// a footer element down the screen, rendering null would work just as well
var child = <div
style={{
height: (typeof document !== 'undefined') ? document.body.clientHeight : 1280
}}
/>
pathname = "animationPlaceholder"
}
return <div>
...
<PanelsTransition
pathname={pathname}
delta={delta}
>
{child}
</PanelsTransition>
...
</div>
}
}
It doesn't work perfectly, I plan on improving it, the direction of the animation is wrong when the animation get's interrupted, but apart from that it seems to work well, the finalAmount bit is a bit of a hacky work in progress, that would likely change once I fix this up.
The react-motion api feels a bit awkward and that maybe I'm not using it the way it's intended especially with the way I've hijacked their data key to keep a reference to the component which then gets rendered, but hell it works.
We looked at a few libs out there for this
currently we're using
https://github.com/maisano/react-router-transition
Great so far ;-)
@stooboo react-router-transition
sadly has the same limitation I mentioned earlier. I tried it today.
@donaldpipowitch cheers I missed that - thanks for the heads-up
Most helpful comment
If like me you're just trying to get simple CSS transitions to work (especially fade-out) with react-router 2.0, and you don't need the heavy artillery, you can use "react-easy-transition", a very lightweight module that I wrote to solve that.
The demo with redux integration