I need some help because I can't seem to understand what I'm doing wrong...
Bellow is my render method from my component and the css used. What I'm trying to do is while the user is not authenticated to see a splash screen instead of the router.
It works but the problem is that there is no fadeIn effect when showing the splash component (mounting), but there is a fadeOut fadeIn effect when transitioning from the splash screen to the router...
render
render = () => {
let { user } = this.props;
return (
<TransitionGroup id='app' enter={true}>
{user.authenticated && <CSSTransition
in={true}
classNames='fade'
key={'router'}
timeout={{ enter: duration, exit: duration }}
>
<Router />
</CSSTransition>}
{!user.authenticated && <CSSTransition
in={true}
classNames='fade'
key={'splash'}
timeout={{ enter: duration, exit: duration }}
>
<Splash />
</CSSTransition>}
</TransitionGroup>
);
}
css
.fade-enter {
opacity: 0.01;
}
.fade-enter.fade-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.fade-exit {
opacity: 1;
}
.fade-exit.fade-exit-active {
opacity: 0.01;
transition: opacity 500ms ease-in;
}
Related
I have a slide in animation that I want to kick in everytime one of the dots in the carousel is clicked. However with my current code the state is always true and if I set it to a "toggling" state, it will be "true,false" meaning the class will only be applied every one click of the dots (when is true).
I want the slide in effect to apply on every dot that is clicked. Code here:
JSX
const [onClick, setClick] = useState(false);
const handleDotClick = () => {
setClick(onClick => !onClick);
};
<div className={classnames(styles.grid, { [styles.slideIn]: onClick })}>
{data[0].map(({ image }) => {
return (
<div>
<Image src={`image.jpg`}/>
</div>
);
})}
</div>
<div className={styles.dots}>
{Array.apply(null, { length: 10 }).map((e, i) => (
<span onClick={handleDotClick}></span>
))}
</div>
CSS
.slideIn {
-webkit-animation: slideIn 0.5s forwards;
-moz-animation: slideIn 0.5s forwards;
animation: slideIn 0.5s forwards;
}
#-webkit-keyframes slideIn {
0% {
transform: translateX(900px);
}
100% {
transform: translateX(0);
}
}
#-moz-keyframes slideIn {
0% {
transform: translateX(900px);
}
100% {
transform: translateX(0);
}
}
#keyframes slideIn {
0% {
transform: translateX(900px);
}
100% {
transform: translateX(0);
}
}
Any idea on how to apply the slideIn class everytime the dot is clicked?
As per my understanding here is an example using useRef to apply the slideIn animation on each time a dot is clicked.
const ref = useRef();
const handleDotClick = () => {
if (ref.current) {
requestAnimationFrame(() => {
ref.current.classList.remove("slideIn");
});
requestAnimationFrame(() => {
ref.current.classList.add("slideIn");
});
}
};
<div ref={ref} className={classnames(styles.grid, { [styles.slideIn]: onClick })}>
{data[0].map(({ image }) => {
return (
<div>
<Image src={`image.jpg`} />
</div>
);
})}
</div>
<div className={styles.dots}>
{Array.apply(null, { length: 10 }).map((e, i) => (
<span onClick={handleDotClick}></span>
))}
</div>
I'm creating a mobile navigation menu and using CSSTransition component from React Transition Group to handle animating in the different levels of my navigation.
I am able to successfully animate in different levels but only in one direction. For instance, the content will enter in from the right and exit to the left. The part that is confusing to me is when i need to change the direction that the content animates in or out.
For my example lets say I have 3 slides
Initially slide #2 would enter in from the right If we go to slide #3 it would exit left.
If we are on slide #3 and want to go back to slide #2 then I want slide #2 to enter in from the right.
import { useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './App.css';
const App = () => {
const [page, setPage] = useState(1);
return (
<>
Page: {page}
<nav>
<button onClick={() => page !== 1 && setPage(page - 1)}>Prev</button>
<button onClick={() => page >= 1 && page <= 2 && setPage(page + 1)}>
Next
</button>
</nav>
<div className='content'>
<CSSTransition
in={page === 1}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideOne />
</CSSTransition>
<CSSTransition
in={page === 2}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideTwo />
</CSSTransition>
<CSSTransition
in={page === 3}
classNames='slide'
timeout={500}
unmountOnExit
>
<SlideThree />
</CSSTransition>
</div>
</>
);
};
const SlideOne = () => {
return <h3>Hello From Slide One</h3>;
};
const SlideTwo = () => {
return <h3>Hello From Slide Two</h3>;
};
const SlideThree = () => {
return <h3>Hello From Slide Three</h3>;
};
export default App;
.content {
width: 200px;
height: 100px;
overflow: hidden;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 1rem;
position: relative;
}
h3 {
position: absolute;
}
.slide-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 0.5s;
}
.slide-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 0.5s;
}
import { useState } from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import "./App.css";
const App = () => {
const [page, setPage] = useState(1);
const [direction, setDirection] = useState("left");
return (
<>
Page: {page}
<nav>
<button
onClick={() => {
page !== 1 && setPage(page - 1);
direction !== "right" && setDirection("right");
}}
>
{" "}
Prev
</button>
<button
onClick={() => {
page >= 1 && page <= 2 && setPage(page + 1);
direction !== "left" && setDirection("left");
}}
>
Next
</button>
</nav>
<div className="content">
{/* <TransitionGroup> */}
<CSSTransition
in={page === 1}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideOne />
</CSSTransition>
<CSSTransition
in={page === 2}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideTwo />
</CSSTransition>
<CSSTransition
in={page === 3}
classNames={`slide-${direction}`}
timeout={500}
unmountOnExit
>
<SlideThree />
</CSSTransition>
{/* </TransitionGroup> */}
</div>
</>
);
};
const SlideOne = () => {
return <h3>Hello From Slide One</h3>;
};
const SlideTwo = () => {
return <h3>Hello From Slide Two</h3>;
};
const SlideThree = () => {
return <h3>Hello From Slide Three</h3>;
};
export default App;
.content {
width: 200px;
height: 100px;
overflow: hidden;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
padding: 1rem;
position: relative;
}
h3 {
position: absolute;
}
.slide-left-enter {
opacity: 0;
transform: translateX(100%);
}
.slide-left-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 2000ms;
}
.slide-left-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-left-exit-active {
opacity: 0;
transform: translateX(-100%);
transition: all 2000ms;
}
.slide-right-enter {
opacity: 0;
transform: translateX(-100%);
}
.slide-right-enter-active {
opacity: 1;
transform: translateX(0%);
transition: all 2000ms;
}
.slide-right-exit {
opacity: 1;
transform: translateX(0%);
}
.slide-right-exit-active {
opacity: 0;
transform: translateX(100%);
transition: all 2000ms;
}
the trick is to reverse the translation on every key button
Please see this codesandbox.
I have a basic framer-motion animation where the height of a box is animated when toggled. However, I want the box to be shown by default, but when the page loads the initial animation is presented.
My question is, how do I avoid having an initial animation for a component if it should be shown on mount, but still maintain future enter and exit animations? Thanks!
I came up with this kind of solution;
1- I took variants to inside of the component
2 - I created two states for opacity and height
3 - States are initially same as where you animate to. So basically nothing happens when you first render.
4 - With useEffect, you can swap the values with the actual initial values, so after first render, the animation works.
export const AnimatedFallback = ({ isVisible }) => {
const [opacity, setOpacity] = useState(1);
const [height, setHeight] = useState("200px");
const variants = {
initial: {
opacity: opacity,
height: height
},
enter: {
opacity: 1,
height: "200px",
transition: { duration: 0.5 }
},
exit: {
opacity: 0,
height: 0,
transition: { duration: 0.5 }
}
};
useEffect(()=> {
setHeight(0)
setOpacity(0)
}, [])
return (
<AnimatePresence>
{isVisible && (
<motion.div
animate="enter"
className="fallback"
exit="exit"
initial="initial"
variants={variants}
>
Suspense Fallback Component
</motion.div>
)}
</AnimatePresence>
);
};
You can check if it is the components first render by:
const firstRender = useRef(true);
useEffect(() => {
if (firstRender.current) {
firstRender.current = false;
return;
}
});
If it is the first render of the component, don't pass the variants to the motion.div. In your example this would look like this:
const variants = {
initial: {
opacity: 0,
height: 0
},
enter: {
opacity: 1,
height: "200px",
transition: { duration: 0.5 }
},
exit: {
opacity: 0,
height: 0,
transition: { duration: 0.5 }
}
};
export const AnimatedFallback = ({ isVisible }) => {
const firstRender = useRef(true);
useEffect(() => {
if (firstRender.current) {
firstRender.current = false;
return;
}
});
return (
<AnimatePresence>
{isVisible && (
<motion.div
animate="enter"
className="fallback"
exit="exit"
initial="initial"
variants={firstRender.current ? {} : variants}
>
Suspense Fallback Component
</motion.div>
)}
</AnimatePresence>
);
};
Bit old of a question, but just in case some people stumble upon this. if you are using an AnimatePresence you can use initial={false} on the AnimatePresence component. Like so,
<AnimatePresence initial={false}>
{someCondition ? (
<motion.h1 {...yourProps}>
yooo
</motion.h1>
) : null}
</AnimatePresence>
More info here:
https://www.framer.com/docs/animate-presence/##suppressing-initial-animations
The simplest method is provided in the framer motion official docs:
specifiy the initial prop with the value as false
<motion.div
...
initial={false}
>
</motion.div>
https://www.framer.com/docs/animation/##enter-animations
For me specifying initial property in motion.div helped
<motion.div initial={{ opacity: 0, height: 0}}>
...
</motion.div>
Looking at the component docs, react components has three items in their lifecycle: mount, unmount and update. React transition groups seems to be the most common way to apply transitions and animations in react. Can it be used on update aswell, (ie statechange) or only when an item is mounted/unmounted?
Yes you can add transition on state change. You need to provide a key to the child element which will change on state update.
From docs :
You must provide the key attribute for all children of ReactCSSTransitionGroup, even when only rendering a single item. This is how React will determine which children have entered, left, or stayed.
Thus, you can do something like this :
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
};
}
handleClick(e){
this.setState({number: this.state.number + 1});
}
render(){
return (
<div className='container'>
<CSSTransitionGroup transitionName="example" transitionAppear={true} transitionAppearTimeout={500} transitionEnterTimeout={500} transitionLeaveTimeout={300}>
<div className="number" key={this.state.number}>{this.state.number}</div>
</CSSTransitionGroup>
<button onClick={this.handleClick.bind(this)}>Click Me!</button>
</div>
)
}
}
React.render(<Container />, document.getElementById('container'));
Css
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.example-leave {
opacity: 1;
}
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
.example-appear {
opacity: 0.01;
}
.example-appear.example-appear-active {
opacity: 1;
transition: opacity .5s ease-in;
}
Here is fiddle.
I have a component that switches child components inside its ReactCSSTransitionGroup when its prop stage changes.
When the page loads the transitionAppear works fine but there is no transition leave or transition enter animation for when the prop changes / when the component inside ReactCSSTransitionGroup changes. When the stage changes from 'CONTENT' to 'BOOKING' the Content component should fade out while Booking component fades in.
Page component:
import React, {PropTypes} from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
export default class Page extends React.Component {
static propTypes = { stage: PropTypes.string };
getComponent(stage) {
switch (stage) {
case 'CONTENT': {
return (
<Content />
);
}
case 'BOOKING': {
return (
<Booking />
);
}
}
}
shouldComponentUpdate(nextProps) {
return nextProps.stage !== this.props.stage;
}
render() {
return (
<div>
<ReactCSSTransitionGroup
transitionName='component-fade'
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}
transitionEnterTimeout={500}
transitionAppearTimeout={500}
transitionLeaveTimeout={500}
component='div'
>
{this.getComponent(this.props.stage)}
</ReactCSSTransitionGroup>
</div>
);
}
}
CSS:
.component-fade-enter {
opacity: 0;
transform: translateY(+2em);
}
.component-fade-enter.component-fade-enter-active {
opacity: 1;
transform: translateY(0em);
transition: opacity 200ms ease-out, transform 200ms ease-in;
}
.component-fade-leave {
transform: translateY(0em);
opacity: 1;
}
.component-fade-leave.component-fade-leave-active {
transform: translateY(+2em);
opacity: 0;
transition: opacity 200ms ease-in, transform 200ms ease-in;
}
.component-fade-appear {
opacity: 0;
transform: translateY(+2em);
}
.component-fade-appear.component-fade-appear-active {
opacity: 1;
transform: translateY(0em);
transition: opacity 200ms ease-out, transform 200ms ease-in;
}
Figured out answer to my own question, just had to add a div wrapper to the components whose key changes as this.props.stage changes
<ReactCSSTransitionGroup
transitionName='component-fade'
transitionAppear={true}
transitionEnter={true}
transitionLeave={true}
transitionEnterTimeout={500}
transitionAppearTimeout={500}
transitionLeaveTimeout={500}
component='div'
>
<div key={this.props.stage}>
{this.getComponent(this.props.stage)}
</div>
</ReactCSSTransitionGroup>