Something this simple should be easily accomplished, yet I\'m pulling my hair out over how complicated it is.
All I want to do is animate the mounting & unmounti
You can use React SyntheticEvent for that.
With events like onAnimationEnd or onTransitionEnd you can accomplish that.
React Docs: https://reactjs.org/docs/events.html#animation-events
Code Example: https://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj
This can be done easily using the CSSTransition
component from react-transition-group
, which is just like the libraries you mentioned. The trick is you need to wrap the CSSTransition component without a show/hide mechanism like you typically would.i.e. {show && <Child>}...
Otherwise you are hiding the animation and it won't work. Example:
ParentComponent.js
import React from 'react';
import {CSSTransition} from 'react-transition-group';
function ParentComponent({show}) {
return (
<CSSTransition classes="parentComponent-child" in={show} timeout={700}>
<ChildComponent>
</CSSTransition>
)}
ParentComponent.css
// animate in
.parentComponent-child-enter {
opacity: 0;
}
.parentComponent-child-enter-active {
opacity: 1;
transition: opacity 700ms ease-in;
}
// animate out
.parentComponent-child-exit {
opacity: 1;
}
.parentComponent-child-exit-active {
opacity: 0;
transition: opacity 700ms ease-in;
}
I think using Transition
from react-transition-group
is probably the easiest way to track mounting/unmounting. It is incredibly flexible. I'm using some classes to show how easy it is to use but you can definitely hook up your own JS animations utilizing addEndListener
prop - which I've had a lot of luck using GSAP with as well.
Sandbox: https://codesandbox.io/s/k9xl9mkx2o
And here's my code.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Transition } from "react-transition-group";
import styled from "styled-components";
const H1 = styled.h1`
transition: 0.2s;
/* Hidden init state */
opacity: 0;
transform: translateY(-10px);
&.enter,
&.entered {
/* Animate in state */
opacity: 1;
transform: translateY(0px);
}
&.exit,
&.exited {
/* Animate out state */
opacity: 0;
transform: translateY(-10px);
}
`;
const App = () => {
const [show, changeShow] = useState(false);
const onClick = () => {
changeShow(prev => {
return !prev;
});
};
return (
<div>
<button onClick={onClick}>{show ? "Hide" : "Show"}</button>
<Transition mountOnEnter unmountOnExit timeout={200} in={show}>
{state => {
let className = state;
return <H1 className={className}>Animate me</H1>;
}}
</Transition>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
If I use Velocity
or AnimeJS
library to animate node directly (instead of css
or setTimeout
), then I found out I can design a hook
to provide the animation status on
and function onToggle
to kick off the animation (ex. slidedown, fade).
Basically what the hook does is to toggle on and off the animation, and afterwards update the on
accordingly. Therefore we can get the status of the animation accurately. Without doing so would reply on a ad-hoc duration
.
/**
* A hook to provide animation status.
* @class useAnimate
* @param {object} _ props
* @param {async} _.animate Promise to perform animation
* @param {object} _.node Dom node to animate
* @param {bool} _.disabled Disable animation
* @returns {useAnimateObject} Animate status object
* @example
* const { on, onToggle } = useAnimate({
* animate: async () => { },
* node: node
* })
*/
import { useState, useCallback } from 'react'
const useAnimate = ({
animate, node, disabled,
}) => {
const [on, setOn] = useState(false)
const onToggle = useCallback(v => {
if (disabled) return
if (v) setOn(true)
animate({ node, on: v }).finally(() => {
if (!v) setOn(false)
})
}, [animate, node, disabled, effect])
return [on, onToggle]
}
export default useAnimate
The usage is the following,
const ref = useRef()
const [on, onToggle] = useAnimate({
animate: animateFunc,
node: ref.current,
disabled
})
const onClick = () => { onToggle(!on) }
return (
<div ref={ref}>
{on && <YOUROWNCOMPONENT onClick={onClick} /> }
</div>
)
and the animate implementation could be,
import anime from 'animejs'
const animateFunc = (params) => {
const { node, on } = params
const height = on ? 233 : 0
return new Promise(resolve => {
anime({
targets: node,
height,
complete: () => { resolve() }
}).play()
})
}