Skip to content

Instantly share code, notes, and snippets.

@mfrancois3k
Forked from Jeyloh/AnimatedSwitch.jsx
Created February 20, 2022 07:56
Show Gist options
  • Save mfrancois3k/7709150a88feb1efab48c152e6b809ce to your computer and use it in GitHub Desktop.
Save mfrancois3k/7709150a88feb1efab48c152e6b809ce to your computer and use it in GitHub Desktop.
React framer-motion route animations
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