Remix-Run Page Transition Animation With Framer Motion - framer-motion

I would like to add some page transitions to my web app.
The problem is that the exit animation doesn't work.
I have a Motion Component
// app/components/Motion.tsx
import React from "react";
import { motion } from "framer-motion";
export default function Motion(props: {
children: React.ReactChild | React.ReactFragment | React.ReactPortal;
}) {
return (
<motion.div
className="w-full h-screen bg-blue-400 flex pt-24 mx-auto"
initial={{ opacity: 0, x: -100 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -100 }}
transition={{ duration: 2 }}
>
{props.children}
</motion.div>
);
}
In which I want to wrap every page like this:
// app/routes/contact.tsx
import Motion from "~/components/Motion";
export default function contact() {
return (
<Motion>
<div>Contact</div>
</Motion>
);
}
I know that I should use the <AnimatePresence> from framer-motion but I don't know where.

Funny enough, I was just trying to figure out how to do this a few hours ago. I'm super new to Remix, but here's a really simple example of what I did to get route animations working.
root.jsx
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from 'remix'
import { AnimatePresence } from 'framer-motion'
import styles from './styles/app.css'
export function meta() {
return {
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
}
}
export function links() {
return [{ rel: 'stylesheet', href: styles }]
}
export default function App() {
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body>
<AnimatePresence exitBeforeEnter>
<Outlet />
</AnimatePresence>
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
)
}
Example of first route index.jsx
import { motion } from 'framer-motion'
export default function Index() {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
className="w-full h-full md:col-span-3 sm:overflow-auto relative z-0"
>
{/* page content */}
</motion.div>
Same <motion.div></motion.div> parent elements as above to be applied to the next route (e.g. about.jsx) - including the same initial, animate, exit, and transition attributes.

Related

Custom cursor always behind elements. How to solve this?

I have created a custom cursor in my NextJS application like this:
import { useEffect, useState } from "react";
import { motion } from "framer-motion";
export default function Cursor(props: any) {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const mouseMove = (e: any) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener("mousemove", mouseMove);
return () => {
window.removeEventListener("mousemove", mouseMove);
};
}, []);
const variants = {
default: {
x: position.x - 16,
y: position.y - 16,
backgroundColor: "#fff",
},
text: {
height: 150,
width: 150,
x: position.x - 75,
y: position.y - 75,
backgroundColor: "#fff",
},
};
return (
<motion.div
variants={variants}
animate={props.cursorVariant}
className="h-[32px] w-[32px] rounded-full fixed top-0 left-0"
/>
);
}
and added it to my index.tsx file as follows:
import HeadTag from "../components/HeadTag";
import Navbar from "../components/Navbar";
import Landing from "../components/Landing";
import AboutMe from "../components/AboutMe";
import Divider from "../components/Divider";
import Projects from "../components/Projects";
import Footer from "../components/Footer";
import ParticleComponent from "../components/Particles";
import Cursor from "../components/Cursor";
import { useState } from "react";
export default function Home() {
const [cursorVariant, setCursorVariant] = useState("default");
return (
<>
<HeadTag />
<div
style={{ scrollBehavior: "smooth" }}
className="select-none bg-slate-900 w-full overflow-x-hidden flex flex-col items-center"
>
<Cursor cursorVariant={cursorVariant}/>
<ParticleComponent />
<Navbar />
<Landing setCursorVariant={setCursorVariant}/>
<AboutMe />
<Divider />
<Projects />
<Divider />
<Footer />
</div>
</>
);
}
Everything works fine regarding the styling and functions of the cursor but when I hover over elements like e.g. my Navbar Toggle, the cursor in in the background of that element and isn't visible anymore.
I have already tried giving the Div element of the cursor a higher z-index than the elements I want to hover over but this in not changing anything...
My global.css looks like this to prevent the default cursor from showing up:
#tailwind base;
#tailwind components;
#tailwind utilities;
* { cursor: none;}
Anyone has a tip for me how to solve this problem? :)
Thanks a lot!

Is there a way to update the children before the framer motion disassembles the items?

I would like to update the content and the border before the element dismounts.
https://codesandbox.io/s/agitated-cerf-siq8e?file=/src/App.js
You are never allowing the content to change as you are testing for true only:
{isOpen && (
The value will never be false within the component.
You can simplify the example by removing AnimatedPresense and introducing useCycle to cycle efficiently between animation variants.
import { motion, useCycle } from "framer-motion";
import * as React from "react";
const variants = {
open: {
height: "auto" ,
transition: { duration: 0.8, ease: "easeInOut" }
},
collapsed: {
height: 0 ,
transition: { duration: 0.8, ease: "easeInOut" }
}
}
export default function App() {
const [variant, toggleVariant] = useCycle('open', 'collapsed');
return (
<div className="App">
<motion.section
style={{ overflow: "hidden" }}
variants={variants}
animate={variant}
>
<div
style={{
border: `5px solid ${variant === 'open' ? "red" : "blue"}`,
height: 100
}}
>
{variant}
</div>
</motion.section>
<button onClick={toggleVariant}>
{variant === 'open' ? 'collapse me': 'expand me'}
</button>
</div>
);
}
Codesandbox

Hide nav button in react-material-ui-carousel

I've just implemented the react material ui carousel, and it was pretty straightforward, the only thing i didn't catch, is how to hide buttons and show them only on over.
I noticed the props navButtonsAlwaysVisible and set it to false but it isn't enough.
Should i implement my own logic for that, or maybe I'm just missing something?
here's the component code:
import styles from '../../styles/Testimonial.module.scss'
import Image from 'next/image'
import Carousel from 'react-material-ui-carousel'
const Testimonial = _ => {
const items = [
{
imageUrl: "/png/image0.webp",
feedback: "feedback0",
name: "name0",
location: "location0"
},
{
imageUrl: "/png/image1.jpeg",
feedback: "feedback1",
name: "name1",
location: "location1"
}
]
return (
<div id="customers" className={`section ${styles.testimonial}`}>
<h2 className={`title ${styles.title}`}>Clientes Felizes</h2>
<span className={"separator"}> </span>
<Carousel
className={styles.carousel}
autoPlay={true}
stopAutoPlayOnHover={true}
interval={5000}
animation={"slide"}
swipe={true}
navButtonsAlwaysVisible={false}
navButtonsProps={{
style: {
backgroundColor: "#8f34eb",
opacity: 0.4
}
}}
>
{
items.map( (item, i) => <Item key={i} item={item} /> )
}
</Carousel>
</div>
)
}
function Item(props)
{
return (
<article className={styles.testimonial__card}>
<div className={styles.testimonial__photo_container}>
<Image
className={styles.testimonial__photo}
src={props.item.imageUrl}
alt="Testimonial"
width={312}
height={300}
/>
</div>
<p className={styles.testimonial__copy}>{props.item.feedback}</p>
<span className={styles.testimonial__name}>{props.item.name}</span>
<span className={styles.testimonial__city}>{props.item.location}</span>
</article>
)
}
export default Testimonial;
there's a prop called navButtonsAlwaysInvisible
navButtonsAlwaysInvisible={true}
You can try using Custom CSS for your purpose. Based on the current rendered markup,
.jss6 {
opacity: 0;
transition: all ease 1000ms; /* So that it does not disappear quickly */
}
You can define the hover for the parent so that it displays only when the parent container is hovered on:
.jss1.Testimonial_carousel__3rny3:hover .jss6 {
opacity: 1;
}
This is how it works now:

How to change the position of an Icon component, within a Tab component?

I'm using MaterialUI's tabs in my React project.
This is the JSX for the tabs:
<AppBar color="default" position="static">
<Tabs indicatorColor="primary" textColor="primary" value={tabIndex} onChange={this.handleChange}>
{instances.map(instance =>
<StyledTab
style={{ textTransform: 'initial' }}
onClick={() => { this.changeActiveInstance(instance.id) }}
label={this.getTabAddress(instance)}
icon={<ClearIcon ></ClearIcon>}
>
</StyledTab>
)}
</Tabs>
This is how i inject the css:
const StyledTab = withStyles({
root: {
textTransform: 'initial'
},
})(Tab);
The result is this:
I would like to position the "ClearIcon" elsewhere. I tried playing with the style injection a bit, with no success.
Can somebody point me to the right direction?
When trying to customize any Material-UI component, the starting point is the CSS portion of the API documentation. The most relevant classes that you may want to override in this case are wrapper, labelContainer, and label.
The best way to fully understand how these are used and how they are styled by default (and therefore what you may want to override) is to look at the source code.
Here are the most relevant portions of the styles from Tab.js:
/* Styles applied to the `icon` and `label`'s wrapper element. */
wrapper: {
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
flexDirection: 'column',
},
/* Styles applied to the label container element if `label` is provided. */
labelContainer: {
width: '100%', // Fix an IE 11 issue
boxSizing: 'border-box',
padding: '6px 12px',
[theme.breakpoints.up('md')]: {
padding: '6px 24px',
},
},
And here is the relevant code for understanding how these are used:
if (labelProp !== undefined) {
label = (
<span className={classes.labelContainer}>
<span
className={classNames(classes.label, {
[classes.labelWrapped]: this.state.labelWrapped,
})}
ref={ref => {
this.labelRef = ref;
}}
>
{labelProp}
</span>
</span>
);
}
<span className={classes.wrapper}>
{icon}
{label}
</span>
Below are some examples of possible ways to customize this.
import React from "react";
import PropTypes from "prop-types";
import Paper from "#material-ui/core/Paper";
import { withStyles } from "#material-ui/core/styles";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
import PhoneIcon from "#material-ui/icons/Phone";
import FavoriteIcon from "#material-ui/icons/Favorite";
import PersonPinIcon from "#material-ui/icons/PersonPin";
const styles = {
root: {
flexGrow: 1,
maxWidth: 700
},
firstIcon: {
paddingLeft: 70
},
labelContainer: {
width: "auto",
padding: 0
},
iconLabelWrapper: {
flexDirection: "row"
},
iconLabelWrapper2: {
flexDirection: "row-reverse"
}
};
class IconLabelTabs extends React.Component {
state = {
value: 0
};
handleChange = (event, value) => {
this.setState({ value });
};
render() {
const { classes } = this.props;
return (
<Paper square className={classes.root}>
<Tabs
value={this.state.value}
onChange={this.handleChange}
variant="fullWidth"
indicatorColor="secondary"
textColor="secondary"
>
<Tab
icon={<PhoneIcon className={classes.firstIcon} />}
label="Class On Icon"
/>
<Tab
classes={{
wrapper: classes.iconLabelWrapper,
labelContainer: classes.labelContainer
}}
icon={<FavoriteIcon />}
label="Row"
/>
<Tab
classes={{
wrapper: classes.iconLabelWrapper2,
labelContainer: classes.labelContainer
}}
icon={<PersonPinIcon />}
label="Row-Reverse"
/>
<Tab icon={<PersonPinIcon />} label="Default" />
</Tabs>
</Paper>
);
}
}
IconLabelTabs.propTypes = {
classes: PropTypes.object.isRequired
};
export default withStyles(styles)(IconLabelTabs);
We have a inbuilt property to set the icon position of tab in material ui. Document link
iconPosition:'bottom' | 'end' | 'start' | 'top'

First time using React-Transition-Group

I have a image viewer component that as one large image and 3 smaller ones below it. When you hover over one of the bottom images it changes the large image to the corresponding image. I would like to add a fade-in/fade-out transition when the larger image changes. So far I can get a fade-in and a fade-out on the mouse completely leaving the smaller image but, not hovering between the images. Here is my code:
import React, { Component } from 'react';
import Transition from 'react-transition-group/Transition'
import Image0 from './Image1.jpg'
import Image1 from './Image2.jpg'
import Image2 from './Image3.jpg'
import './ImageViewer.css';
const duration = 300;
const Images = [Image0, Image1, Image2]
const defaultStyle = {
transition: `opacity ${duration}ms ease-in-out`,
opacity: 0,
}
const transitionStyles = {
entering: { opacity: 1 },
entered: { opacity: 1 },
exiting: { opacity: .9 },
exited: { opacity: 0.01 }
};
const Fade = ({ in: inProp, currentImage }) => (
<Transition in={inProp} timeout={duration}>
{(state) => (
<div style={{
...defaultStyle,
...transitionStyles[state]
}}>
<img src={currentImage}/>
</div>
)}
</Transition>
);
class App extends Component {
state = { show: false, currentImg: Images[0] }
handleClick = e => {
this.setState({
show: !this.state.show,
currentImg: Images[Number(e.currentTarget.dataset.id)]
})
}
render() {
return (
<div className="App">
<Fade in={this.state.show} currentImage={this.state.currentImg}/>
<div className="small_image_wrapper">
<img className="small_image"
src={Image0}
alt="A wonderful bed"
data-id={0}
onMouseOver={this.handleClick}
onMouseOut={this.handleClick}
/>
<img className="small_image"
src={Image1}
alt="A wonderful bed"
data-id={1}
onMouseOver={this.handleClick}
onMouseOut={this.handleClick}
/>
<img className="small_image"
src={Image2}
alt="A wonderful bed"
data-id={2}
onMouseOver={this.handleClick}
onMouseOut={this.handleClick}
/>
</div>
</div>
);
}
}
export default App;
Also here's a link to what I have so far with transitions:
https://peaceful-jones-ee2ca2.netlify.com/
And without transitions:
https://practical-lamport-9cc94e.netlify.com/
I am not sure but you have to wrap all your images into TransitionGroup
import {TransitionGroup}, {Transition} from 'react-transition-group';
...
render() {
return (
<div className="App">
<TransitionGroup>
<Fade in={this.state.show} currentImage={this.state.currentImg}/>
</TransitionGroup>
<div className="small_image_wrapper">
/* here comes small images */
</div>
</div>
)};

Resources