How can I animate when rendering and unmount a component in React? - javascript

I trying animate a component when rendering and unmount it in React, but unsuccessfully.
Component to animate
One of the ways I've tried was create a state (fade) with the class of animation property, when rendering, the class is fadeIn and on will unmount the class is fadeOut
const handlePokemonDetails = async () => {
if (pokemonDetails === true){
setFade(styles.fadeOut)
await timeout(500)
}
else{
setFade(styles.fadeIn)
}
setPokemonDetails(!pokemonDetails)
}
...
<div className={pokemonDetails ? styles.pokemonDetails + " " + fade : ""}></div>
SASS code:
.pokemonDetails
position: absolute
top: 0
z-index: 3
display: flex
width: 35vmin
height: 39.5vmin
flex-direction: column
background-color: rgba(210, 210, 210, 0.85)
border-radius: 10px
#keyframes fade
0%
height: 0vmin
100%
height: 39.5vmin
.fadeIn
animation: fade 0.5s normal
.fadeOut
animation: fade 0.5s reverse
But, when I change state to other class, the component don't apply this new class animation style. Does anyone know how I can fix this or another way to do it?

Good question. I just finished writing hook for React that helps with that
look at https://github.com/perymimon/React-Anime-Manager
Just publish ver2-alpha so the documentation is still lake.
import {useAnimeManager,ADDED,REMOVED} from "#perymimon/react-anime-manager"
const phase2class = {
[ADDED]: "fadeIn",
[REMOVED]: "fadeOut",
}
function Pokemons({pokemones}){
const pokemonesStates = useAnimeManager(pokemones);
return (
<pokemones>
{ pokemonesStates.map(state =>(
<div className={phase2class[state.phase] } onAnimationEnd={state.done}/>
)
)}
</pokemones>
)
}

This is the exact use case for react-transition-group.
Wrap your component inside <CSSTransition>
function App() {
const [inProp, setInProp] = useState(false);
return (
<div>
<CSSTransition in={inProp} timeout={200} classNames="my-node">
<div>
{"I'll receive my-node-* classes"}
</div>
</CSSTransition>
<button type="button" onClick={() => setInProp(true)}>
Click to Enter
</button>
</div>
);
}
Then add CSS to your classes. If you set className of CSSTransition to my-node, you will have 4 classes to style:
.my-node-enter {
opacity: 0;
}
.my-node-enter-active {
opacity: 1;
transition: opacity 200ms;
}
.my-node-exit {
opacity: 1;
}
.my-node-exit-active {
opacity: 0;
transition: opacity 200ms;
}

Related

Animate transition on a react component that i hide on re-render

i m using reactjs and i have a datepicker component that i hide when the user clicks outside of the component element.
the code snippet is like so:
`
class DatePicker extends Component{
constructor(props){
super(props)
state= { focus: false } // when focus: false i hide the dates component
}
.......
render(){
const { focus } = this.state
return(
<input type="text" placeholder="start date">
<input type="text" placeholder="start date">
{focus && <DatesContainer ...props>} // if focus==false i dont show the <DatesContainer> component.I need to hide it with fade out or something.
)
}
}`
So when the user clicks outside of the <DatesContainer/> the state.focus updates to false, the re-renders and, this time, the <DatesContainer/> is not render at all, so far so good. But i need to hide it with a 0.3s animation.
Any opinions on that ?
The difference between my answer and previous is you're able to set specific actions to certain animation keyframes.
The key point is to provide animation name as a function parameter
CSS
.Modal {
position: fixed;
top: 30%;
left: 25%;
transition: all 0.3s ease-out;
}
.ModalOpen {
animation: openModal 0.4s ease-out forwards;
}
.ModalClosed {
animation: closeModal 0.4s ease-out forwards;
}
#keyframes openModal {
0% { transform: translateY(-100%); }
100% { transform: translateY(0); }
}
#keyframes closeModal {
0% { transform: translateY(0); }
100% { transform: translateY(-100%);}
}
JS
const modal = ({
isShown, isMounted,
initiateUnmountAction, unmountAction
}) => {
const cssClasses = [
"Modal",
props.isShown ? "ModalOpen" : "ModalClosed"
];
return (
<Fragment>
{isMounted && <div className={cssClasses.join(' ')}
onAnimationEnd={event =>
{event.animationName == "closeModal" && unmountAction}
}>
<h1>A Modal</h1>
<button className="Button" onClick={initiateUnmountAction}>
Dismiss
</button>
</div>}
</Fragment>
);
};
I'd update the state to have a transitioning property. Using CSS animations or transitions, and some conditional rendering, we can apply a new class to the DatesContainer, detect when an animation is finished and change the focus state to false.
You'll have to set transitioning to true in the state whenever you click outside it.
It may look something like this:
CSS
#keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.fading {
animation-name: fadeOut;
animation-duration: 0.3s;
animation-fill-mode: forwards;
will-change: opacity;
}
JS
//import your styles here
class DatePicker extends Component{
constructor(props){
super(props)
state= { focus: false, transitioning: false }
}
handleFade() {
this.setState({transitioning: false})
}
render(){
const { focus, transitioning } = this.state
return(
<input type="text" placeholder="start date">
<input type="text" placeholder="start date">
{focus && <DatesContainer
className={transitioning === true ? "fading" : null}
onAnimationEnd={transitioning === true ? () => this.handleFade() : null}
/>
}
)
}
}`

How to use a different delay for each item with React transition group?

I am animating the entry and exit of an array of items using TransitionGroup and CSSTransition (with a fade effect). I would like the items to appear with a slight delay between them instead of all at the same time. Note that the delay can be lower than the duration of the animation.
With my current code, all the items are fading-in at the same time (as expected). In my render function, I have the following to animate the entry and exit of my components:
<TransitionGroup>
items.map((item,index) => (
<CSSTransition
key={item.id}
timeout={1000}
classNames="fade"
<ItemPreview item={item} />
</CSSTransition>
))
</TransitionGroup>
And the CSS:
.fade-enter{
opacity: 0;
visibility: hidden;
transition: all ease 1s;
}
.fade-enter.fade-enter-active{
opacity: 1;
visibility: visible;
transition: all ease 1s;
}
.fade-exit {
visibility: visible;
opacity: 0;
}
.fade-exit.fade-exit-active{
opacity: 0;
visibility: hidden;
transition: all ease 1s;
}
How would I go about adding a different delay for each item?
I solved my issue by adding a transitionDelay in the style of my ItemPreview as well as changing the timeout dynamically for each item.
The tricky part is to calculate the actual delay for each item, especially when loading new items afterwards. I ended up adding a isNew field in my items in the reducer which is set to true only for newly loaded items. This is not ideal as it involves changing my data just for animations.
render(){
const { items } = this.props;
let delay_index = -1;
let delay_jump = 100;
return (
<TransitionGroup>
items.map((item,index) => {
delay_index += offer.isNew ? 1 : 0;
const delay = Math.max(0, delay_index*100);
return (
<CSSTransition
key={item.id}
timeout={1000 + delay}
classNames="fade">
<ItemPreview
item={item}
style={{transitionDelay: `${delay}ms`}} />
</CSSTransition>
)
})
</TransitionGroup>
)
}
I might be too late but I was facing the same issue with entering animation and my solution might be useful for someone else.
I'm using <Transition> and I've solved using a for loop in SCSS:
.fade {
opacity: 0;
visibility: hidden;
transition: opacity 0.5s ease-in, visibility 0.5s;
// adjust for loop to total number of elements
#for $i from 1 through 5 {
&:nth-child(#{$i}n) {
transition-delay: #{$i * 0.25}s;
}
}
&.entering,
&.entered {
opacity: 1;
visibility: visible;
}
}

How to run a simple toggle function on load (React)?

I'm trying to get my sidebar to slide in by toggling the state of my Sidebar (going from open: false to open: true) on component load. I'm doing this is an attempt to get a slide-in effect once the user loads the page.
Unfortunately, the way I'm currently doing this, it appears as though the state is changed and the component re-rendered essentially immediately, preventing the slide-in effect from happening. Here's my code:
export default class Sidebar extends React.Component {
constructor(props) {
super(props);
this.state = {open: false};
}
componentDidMount() {
this.setState({open: !this.state.open})
}
handleToggle = () => this.setState({open: !this.state.open});
render() {
return (
<div>
<Drawer width={'25%'} open={this.state.open}>
<AppBar title="Wealth Management" onLeftIconButtonClick={this.handleToggle}/>
<MenuItem>Menu Item</MenuItem>
<MenuItem>Menu Item 2</MenuItem>
</Drawer>
</div>
);
}
}
How do you get this to work? Am I updating on the wrong lifecycle method?
It is called CSS transition.
You could have a look at this medium post or check the MDN documentation.
We use a sidebar that slide open or closed based on user settings, the trick is to set a negative margin on the base css-class "sidebar" in this case.
Then add/remove the toggle class (on render or button event). That is all that is required to get a smoth animation on load
css-classes:
.sidebar {
z-index: 1000;
position: fixed;
left: 200px;
top: 0px;
bottom: 0px;
width: 250px;
margin-left: -250px;
-webkit-transition: all 0.5s ease;
-moz-transition: all 0.5s ease;
-o-transition: all 0.5s ease;
transition: all 0.5s ease;
}
.sidebarToggled {
width: 65px;
}

Implement a SlideToggle Functionality with React.js

I'd like to accomplish the following with my drop down menu.
1 - Show it upon click
2 -Hide it on second click
3 - Hide it when clicking anywhere outside of it.
4 - Do all that with a slide effect
I've got 1-3 covered. I'm blocked on 4.
How would I create a slide effect along with the following click event happening bellow?
I've got a working proof of concept using jQuery's slideToggle (not shown here)... however, I'd like to learn how to do it in the react way.
in case you'd like to see the full the code:
react drop-down nav bar
// CASE 1 Show Hide on click, no slide effect yet
class ServicesDropdown extends Component {
constructor() {
super();
this.state = {
dropdown: false
};
}
handleClick = () => {
if (!this.state.dropdown) {
// attach/remove event handler
document.addEventListener('click', this.handleOutsideClick, false);
} else {
document.removeEventListener('click', this.handleOutsideClick, false);
}
this.setState(prevState => ({
dropdown: !prevState.dropdown,
}));
}
handleOutsideClick = (e) => {
// ignore clicks on the component itself
if (this.node.contains(e.target)) {
return;
}
this.handleClick();
}
render() {
return (
<li ref={node => { this.node = node; }}>
<a href="#!" onClick={this.handleClick}>Services +</a>
{this.state.dropdown &&
(
<ul className="nav-dropdown" ref={node => { this.node = node; }}>
<li>Web Design</li>
<li>Web Development</li>
<li>Graphic Design</li>
</ul>
)}
</li>
)
}
}
A while ago I figured out how to apply a slide-down effect to a React component, it's not exactly the same behavior but you might find my code & description useful. See my answer to a different, related question here: https://stackoverflow.com/a/48743317/1216245 [Edit: It was deleted since then, so I'm pasting the description below.]
The blog post is here: http://blog.lunarlogic.io/2018/slidedown-menu-in-react/. Feel free to steal any part of the code.
Here's a short description of the most important parts of the solution.
As for the React/JSX part, you wrap the component that you'd like to slide in a CSSTransitionGroup. (You can read more about this React Add-on here: https://reactjs.org/docs/animation.html#high-level-api-reactcsstransitiongroup and here: https://reactcommunity.org/react-transition-group/.)
<div className="component-container">
<CSSTransitionGroup
transitionName="slide"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}
>
{ this.state.showComponent && <Component /> }
</CSSTransitionGroup>
</div>
Note that it's all wrapped in a container, which you'll need for the animation to work like you'd like it to.
And here is the CSS I used for the slide animation effect:
/*
Slide animation styles.
You may need to add vendor prefixes for transform depending on your desired browser support.
*/
.slide-enter {
transform: translateY(-100%);
transition: .3s cubic-bezier(0, 1, 0.5, 1);
&.slide-enter-active {
transform: translateY(0%);
}
}
.slide-leave {
transform: translateY(0%);
transition: .3s ease-in-out;
&.slide-leave-active {
transform: translateY(-100%);
}
}
/*
CSS for the submenu container needed to adjust the behavior to our needs.
Try commenting out this part to see how the animation looks without the container involved.
*/
.component-container {
height: $component-height; // set to the width of your component or a higher approximation if it's not fixed
min-width: $component-width; // set to the width of your component or a higher approximation if it's not fixed
}
For the full example & demo check out http://blog.lunarlogic.io/2018/slidedown-menu-in-react/.
What aboot using CSS transitions?
UI Animations with React — The Right Way
As an example I found this slide effect that could be implemented on your navbar.
.wrapper {
position: relative;
overflow: hidden;
width: 100px;
height: 100px;
border: 1px solid black;
}
#slide {
position: absolute;
left: -100px;
width: 100px;
height: 100px;
background: blue;
-webkit-animation: slide 0.5s forwards;
-webkit-animation-delay: 2s;
animation: slide 0.5s forwards;
animation-delay: 2s;
}
#-webkit-keyframes slide {
100% { left: 0; }
}
#keyframes slide {
100% { left: 0; }
}
<div class="wrapper">
<img id="slide" src="https://cdn.xl.thumbs.canstockphoto.com/-drawings_csp14518596.jpg" />
</div>
I hope it helps :)

How to rerun css3 animations in react js

I need to show a quick flash message for few seconds after ajax success. The message should be auto hidden after 1/2 seconds.
As reactjs does not promote for dom manipulation, I want to achieve the same using css3 animation.
Here is simple demo i prepared,
When you click on the button for the first time it shows the message, but on the subsequent click it does not do anything.
class Test extends React.Component {
state = {
show: false
}
showFlashMessage() {
this.setState({
show: true
})
}
render() {
return ( <
div >
<
div className = {
`message ${this.state.show ? "fadeOut": ""}`
} > Hello < /div> <
button id = "but"
onClick = {
this.showFlashMessage.bind(this)
} > Click Me < /button> <
/div>
)
}
}
ReactDOM.render( <
Test / > ,
document.getElementById('container')
);
#keyframes FadeAnimation {
0% {
opacity: 1;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
.fadeOut {
animation: FadeAnimation 1s ease-in .4s forwards;
}
.message {
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
JS Fiddle: http://jsfiddle.net/2kqv6fo7/9/
I am not sure if there is any other alternatives except css3 .
Any suggestion?
As I have answered to your previous question also, its the same, try handling with the state change. Since you have mentioned you are using a toggle button in your previous question, i have used a similar checkbox which will be helpful for you.
I ve made the code work only when the toggle is checked, I assume you would need to show some message on toggle off too. Do the same, by toggling between two classes. And try to avoid inline function bindings, cuz it will trigger for every render. Instead go for constructor, which will get executed just once when the component is loaded.
class Test extends React.Component {
constructor() {
super();
this.state = {
show: false,
showClass: undefined
}
this.showFlashMessage = (event) => this._showFlashMessage(event);
}
_showFlashMessage(event) {
this.setState({
show: !this.state.show,
showClass: (!this.state.show ? "fadeOut": "fadeOut2")
})
}
render() {
return ( <
div >
<
div className = {
`message ${this.state.showClass}`
} > Hello < /div>
<button id ="but" onClick = {this.showFlashMessage} > Click Me </button><
/div>
)
}
}
ReactDOM.render( <
Test / > ,
document.getElementById('container')
);
#keyframes FadeAnimation {
0% {
opacity: 1;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
#keyframes FadeAnimation2 {
0% {
opacity: 1;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
.fadeOut {
opacity: 1;
visibility: visible;
animation: FadeAnimation 1s ease-in .2s forwards;
}
.fadeOut2 {
opacity: 1;
visibility: visible;
animation: FadeAnimation2 1s ease-in .2s forwards;
}
.message {
opacity: 0;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
You should capture the end of the animation and change the state there.
See this:
How can I animate a react.js component onclick and detect the end of the animation

Categories