page transitions without React-Router
OK. Well, I struggled with this for WAAAAY too long. I finally dumped react-transition-group, and went pure CSS. Here's my solution.
PageSlider.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
require('./transitions.scss');
const BlankPage = (props) => <div className="placeholder"></div>;
class PageSlider extends Component {
constructor(props) {
super(props);
this.state = {
nextRoute: props.page,
pages: {
A: {key: 'A', component: BlankPage, className: 'placeholder'},
B: {key: 'B', component: BlankPage, className: 'placeholder'},
},
currentPage: 'A'
};
}
componentDidMount() {
//start initial animation of incoming
let B = {key: 'b', component: this.state.nextRoute, className: 'slideFromRight'}; //new one
let A = Object.assign({}, this.state.pages.A, {className: 'slideOutLeft'}); //exiting
this.setState({pages: {A: A, B: B}, currentPage: 'B'});
}
componentWillReceiveProps(nextProps) {
if (nextProps.page != this.state.nextRoute) {
this.transition(nextProps.page, nextProps.fromDir);
}
}
transition = (Page, fromDir) => {
if (this.state.nextRoute != Page) {
let leavingClass, enteringClass;
let pages = Object.assign({}, this.state.pages);
const current = this.state.currentPage;
const next = (current == 'A' ? 'B' : 'A');
if (fromDir == "right") {
enteringClass = 'slideFromRight';
leavingClass = 'slideOutLeft';
} else {
enteringClass = 'slideFromLeft';
leavingClass = 'slideOutRight';
}
pages[next] = {key: 'unique', component: Page, className: enteringClass};
pages[current].className = leavingClass;
this.setState({pages: pages, nextRoute: Page, currentPage: next});
}
}
render() {
return (
<div id="container" style={{
position: 'relative',
minHeight: '100vh',
overflow: 'hidden'
}}>
{React.createElement('div', {key: 'A', className: this.state.pages.A.className}, <this.state.pages.A.component />)}
{React.createElement('div', {key: 'B', className: this.state.pages.B.className} , <this.state.pages.B.component />)}
</div>
);
}
}
PageSlider.propTypes = {
page: PropTypes.func.isRequired,
fromDir: PropTypes.string.isRequired
};
export default PageSlider;
transition.scss
.placeholder {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
background: transparent;
-webkit-animation: slideoutleft 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutleft 0.5s forwards;
animation-delay: 10;
}
.slideFromLeft {
position: absolute;
left: -100vw;
width: 100vw;
height: 100vh;
-webkit-animation: slidein 0.5s forwards;
-webkit-animation-delay: 10;
animation: slidein 0.5s forwards;
animation-delay: 10;
}
.slideFromRight {
position: absolute;
left: 100vw;
width: 100vw;
height: 100vh;
-webkit-animation: slidein 0.5s forwards;
-webkit-animation-delay: 10;
animation: slidein 0.5s forwards;
animation-delay: 10;;
}
.slideOutLeft {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
-webkit-animation: slideoutleft 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutleft 0.5s forwards;
animation-delay: 10;
}
.slideOutRight {
position: absolute;
left: 0;
width: 100vw;
height: 100vh;
-webkit-animation: slideoutright 0.5s forwards;
-webkit-animation-delay: 10;
animation: slideoutright 0.5s forwards;
animation-delay: 10;
}
@-webkit-keyframes slidein {
100% { left: 0; }
}
@keyframes slidein {
100% { left: 0; }
}
@-webkit-keyframes slideoutleft {
100% { left: -100vw; opacity: 0 }
}
@keyframes slideoutleft {
100% { left: -100vw; opacity: 0}
}
@-webkit-keyframes slideoutright {
100% { left: 100vw; opacity: 0}
}
@keyframes slideoutright {
100% { left: 100vw; opacity: 0}
}
Passing in the next component, which is my react Page component, called like so:
app.js
<div id="app">
<PageSlider page={this.state.nextRoute} fromDir={this.state.fromDir}/>
</div>
How to add page transitions to React without using the router?
I figured it out! Your CSS animations are trying to use fadeIn, but that's not a CSS property. You need to change it to opacity. Like so:
//Page transition
.pageTransition-enter {
opacity: 0.01;
}
.pageTransition-enter.pageTransition-enter-active {
animation: opacity 1s ease-in;
}
.animation-leave {
opacity: 1;
}
.pageTransition-leave.pageTransition-leave-active {
animation: opacity 3s ease-in;
}
.pageTransition-appear {
opacity: 0.01;
}
.pageTransition-appear.pageTransition-appear-active {
animation: opacity 5s ease-in;
}
React router dom v6 prevent transition without Prompt
You can but it requires using a custom history
object and HistoryRouter
. For this history@5
needs to be a package dependency.
Import version 5 of
history
.npm i -S history@5
Create and export a custom
history
object.import { createBrowserHistory } from 'history';
export default createBrowserHistory();Import and render a
HistoryRouter
and passhistory
as a prop....
import { unstable_HistoryRouter as Router } from "react-router-dom";
import history from './history';
...
<Router history={history}>
<App />
</Router>Follow the docs for blocking transitions.
// Block navigation and register a callback that
// fires when a navigation attempt is blocked.
let unblock = history.block((tx) => {
// Navigation was blocked! Let's show a confirmation dialog
// so the user can decide if they actually want to navigate
// away and discard changes they've made in the current page.
let url = tx.location.pathname;
if (window.confirm(`Are you sure you want to go to ${url}?`)) {
// Unblock the navigation.
unblock();
// Retry the transition.
tx.retry();
}
});
Demo:
Transition page content excluding the header and footer
Assuming your header and footer is the same for all the pages, you should move the header and footer out of your Page components, and out of the <AnimatePresence>
component.
<Header />
<AnimatePresence initial={false} exitBeforeEnter>
<Switch location={location} key={location.pathname}>
{/* Only the main content gets animated. No headers / footers in here. */}
</Switch>
</AnimatePresence>
<Footer />
Transition between routes in react-router-dom v6.3
I think your solution is close to working. Move the PageLayout
component and motion.div
into a layout route that uses the current path as a React key.
Example:
import { Outlet } from 'react-router-dom';
const AnimationLayout = () => {
const { pathname } = useLocation();
return (
<PageLayout>
<motion.div
key={pathname}
initial="initial"
animate="in"
variants={pageVariants}
transition={pageTransition}
>
<Outlet />
</motion.div>
</PageLayout>
);
};
...
<Routes>
<Route element={<AnimationLayout />}>
<Route path="/audit" element={<Audit />} />
<Route path="/smartx" element={<SmartX />} />
<Route path="/faq" element={<FAQ />} />
<Route path="/support" element={<Support />} />
<Route path="/terms" element={<Terms />} />
<Route path="/policy" element={<Policy />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<Navigate to="/audit" replace />} />
</Route>
</Routes>
react-transition-group SwitchTransition doesn't work on first update but on all others
To answer my own question: I couldn't figure out the reason, but I suppose the problem had to do with the key property of the transition component. I just passed in the entire page (state) object, and apparently, that causes problems. When I used a subset of the object as key (like page.id, or pages[0].id) it started working correctly in all instances.
Related Topics
Adding Class to React Component After a Certain Amount of Time
Strange Border-Width Behavior in Chrome - Floating Point Border-Width
Introjs Bootstrap Menu Doesn't Work
How to Get The System Accent Color for Uwp-Apps
<Style> and <Script> Tags Are Displayed Physically on Page
Make Fixed Header Scroll Horizontal
How to Enable SASS Line Numbers in CSS Output
Angular [Class.Active]="Isactive" - What Does "Class.Active" Mean Here
Negative Margins in CSS: Good Tutorial and Tricks Site
Options to Solve Browser Compatibility Issues
Padding Between Checkbox and Label
Is There a Way (Or a Plugin) to Make Vim Generate a Code Outline for CSS
Use CSS to Alternate Ul Bullet Point Styles
CSS Fonts: Howto Convert Multiple Ttf Files into One File