-
-
Save mfrancois3k/7709150a88feb1efab48c152e6b809ce to your computer and use it in GitHub Desktop.
React framer-motion route animations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React from "react"; | |
import { Switch } from "react-router-dom"; | |
import { useSelector } from "react-redux"; | |
import { AnimatePresence, motion } from "framer-motion"; | |
import { getPlatform, isMobilePlatform } from "../util/browser.helper"; | |
import styled from "styled-components"; | |
import { ROUTES } from "constants/routes.const"; | |
// This can be tweaked for speed and opacity on enter-exit animations. | |
const variants = { | |
enter: reverse => ({ | |
x: reverse ? "-100%" : "100%", | |
transition: { | |
duration: 0.3, | |
ease: "easeInOut", | |
}, | |
}), | |
// default state, centered with full opacity | |
center: { | |
x: 0, | |
opacity: 1, | |
transition: { | |
duration: 0.3, | |
ease: "easeInOut", | |
}, | |
}, | |
// By removing this exit animation, all routes will only transition on enter. | |
// That would create a "slide-over" effect for routes | |
exit: reverse => ({ | |
zIndex: 0, | |
x: reverse ? "100%" : "-100%", | |
transition: { | |
duration: 0.3, | |
ease: "easeInOut", | |
}, | |
}), | |
}; | |
/** | |
* This component controls page animations for sliding in and out. | |
* It uses framer-motion to set these animations. The ContextRouteAnimation is a framer-motion component | |
* It decides the different direction through the connected-redux-router package, which provides a router reducer. | |
* By checking for POP as an action, we can slide out instead of in. | |
* @param children | |
* @param rest | |
* @returns {*} | |
* @constructor | |
*/ | |
const AnimatedSwitch = ({ children, ...rest }) => { | |
// TODO: This is from redux and should be replaced by a history-listener which listens to POP events on history, | |
// POP events are used for animating backwards | |
const { action, location } = useSelector(state => state.router); | |
const reverse = action.includes("POP"); | |
const noPageAnimations = React.useMemo( () => { | |
!isMobilePlatform() || process.env.NODE_ENV === "test"; | |
}, []) | |
// Exclude animations in test | |
if (noPageAnimations) { | |
return ( | |
<Switch location={location} {...rest}> | |
{children} | |
</Switch> | |
); | |
} | |
return ( | |
<AnimatePresence custom={reverse} initial={false}> | |
<ContextRouteAnimation | |
id={"page-wrapper"} | |
variants={variants} | |
initial={"enter"} | |
animate={"center"} | |
exit={"exit"} | |
key={location.pathname} | |
custom={reverse} | |
> | |
<Switch location={location} {...rest}> | |
{children} | |
</Switch> | |
</ContextRouteAnimation> | |
</AnimatePresence> | |
); | |
}; | |
export default React.memo(AnimatedSwitch); | |
// This style here kinda "hijacks" the layout so that animations will work perfectly, and always start new routes at the top of the application. It will look buggy without it, but it also breaks features such as "window.scrollIntoView()" | |
export const ContextRouteAnimation = styled(motion.div)` | |
height: 100vh; | |
overflow-x: hidden !important; | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100vw; | |
${getPlatform() === "iOS" && | |
` | |
-webkit-overflow-scrolling: touch; | |
`} | |
`; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment