Animate React Modal - css

I have a Modal in my web app which I want to slide in when the user clicks "open" button. I am using react-transition-group to achieve this. But I am unable to get the animation working. Not sure what's wrong here?
import React from 'react';
import ReactDOM from 'react-dom';
import { CSSTransition } from 'react-transition-group';
import Modal from 'react-modal';
import './styles.css';
class Example extends React.Component {
state = {
isOpen: false,
};
toggleModal = () => {
this.setState(prevState => ({
isOpen: !prevState.isOpen,
}));
};
render() {
const modalStyles = {
overlay: {
backgroundColor: '#ffffff',
},
};
return (
<div>
<button onClick={this.toggleModal}>
Open Modal
</button>
<CSSTransition
in={this.state.isOpen}
timeout={300}
classNames="dialog"
>
<Modal
isOpen={this.state.isOpen}
style={modalStyles}
>
<button onClick={this.toggleModal}>
Close Modal
</button>
<div>Hello World</div>
</Modal>
</CSSTransition>
</div>
);
}
}
CSS File:
.dialog-enter {
left: -100%;
transition: left 300ms linear;
}
.dialog-enter-active {
left: 0;
}
.dialog-exit {
left: 0;
transition: left 300ms linear;
}
.dialog-exit-active {
left: -100%;
}
Here is the working code.

The problem here is that react-modal uses a react-portal as its mounting point. So it is not rendered as the children of the CSSTransition component. So you could not use the CSSTransition with it.
You can use some custom css to create transition effects. It is in the react-modal documentation.
So if you add this to your css. There will be transition.
.ReactModal__Overlay {
opacity: 0;
transform: translateX(-100px);
transition: all 500ms ease-in-out;
}
.ReactModal__Overlay--after-open {
opacity: 1;
transform: translateX(0px);
}
.ReactModal__Overlay--before-close {
opacity: 0;
transform: translateX(-100px);
}
And we have to add the closeTimeoutMS prop to the modal in order the closing animation to work.
<Modal
closeTimeoutMS={500}
isOpen={this.state.isOpen}
style={modalStyles}
>
https://codesandbox.io/s/csstransition-component-forked-7jiwn

Related

Position one image over another on react in a specific location and animate

I Want to reply a behavior I saw on a page the map below the section "How It works" title (I wanted to embed a video but it seems stack overflow don't allow it) i.e place one image over another in a specific location, the page I linked shows a map and a pointer that points to the emirates but I can't figure how to do this I achieve this in React, by using position CSS property to position the second image (pointer) relative to the first image (map) and then use the top, left, right, and bottom properties to position the pointer on the desired location on the map but I am having problems with the animation, this is rendering when the parent is rendered and not when the page is scrolled down, I am using the IntersectionObserver
my code is the following:
import React from 'react';
import React, {useEffect, useRef, useState} from 'react';
function MapWithPointer() {
const mapRef = useRef(null);
const pointerRef = useRef(null);
const [isPointerVisible, setIsPointerVisible] = useState(false);
useEffect(() => {
if(!isPointerVisible) return;
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
setIsPointerVisible(true);
}
});
if(mapRef.current) observer.observe(mapRef.current);
return () => observer.disconnect();
}, [isPointerVisible, mapRef]);
return (
<div ref={mapRef} style={{ position: "relative" }}>
<img src="https://emiride.com/wp-content/uploads/2022/11/map-1024x594.png" alt="map" />
<img
ref={pointerRef}
src="https://emiride.com/wp-content/uploads/2022/11/piont.png"
alt="pointer"
width={'30wh'}
className={`pointer ${isPointerVisible ? "is-visible" : ""}`}
style={{ position: "absolute", top: "310px", left: "640px" }}
/>
</div>
);
}
css:
#keyframes pointer-animation {
from {
transform: translateY(1000%);
}
to {
transform: translateY(0);
}
}
.pointer {
animation: pointer-animation 1s ease-in-out;
transform: translateY(100%);
}
.pointer.is-visible {
transform: translateY(0);
animation: pointer-animation 1s ease-in-out;
}
and int the parent ins rendering like this:
<MapComponent/> , the parent does not have useEffect hook.
how can I achieve my purpose?

"appear" in CSSTransition doesn't work as expected

I'd like to implement CSSTransition to animate an element but still mount it right away.
Based on the documentation:
By default the child component does not perform the enter transition when it first mounts, regardless of the value of in. If you want this behavior, set both appear and in to true.
So I would think that this example would work:
<CSSTransition
in={showMessage}
appear={showMessage}
timeout={300}
classNames="alert"
>
.alert-enter,
.alert-appear {
opacity: 0;
transform: scale(0.9);
}
.alert-enter-active,
.alert-appear-active {
opacity: 1;
transform: translateX(0);
transition: opacity 300ms, transform 300ms;
}
.alert-exit {
opacity: 1;
}
.alert-exit-active {
opacity: 0;
transform: scale(0.9);
transition: opacity 300ms, transform 300ms;
}
Here is a codesandbox: https://codesandbox.io/s/keen-forest-ghgxq?file=/index.js
As you can see it is displayed right away. I need it to be hidden but still mounted. How do I do this?
But you didn't setup the show property of the Alert component. So by default, it is true
Here's how you can hide it in the beginning, then show and unshow, sandbox
Code:
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { Container, Button, Alert } from 'react-bootstrap';
import { CSSTransition } from 'react-transition-group';
import './styles.css';
function Example() {
const [showMessage, setShowMessage] = useState(false);
useEffect(() => {
setTimeout(() => {
setShowMessage(true);
}, 10000);
}, []);
return (
<Container style={{ paddingTop: '2rem' }}>
<CSSTransition
in={showMessage}
exit={showMessage}
appear={showMessage}
timeout={300}
classNames="alert"
>
<Alert
variant="primary"
dismissible
show={showMessage}
onClose={() => setShowMessage(false)}
>
<Alert.Heading>
Animated alert message
</Alert.Heading>
<p>
This alert message is being transitioned in and
out of the DOM.
</p>
<Button onClick={() => setShowMessage(false)}>
Close
</Button>
</Alert>
</CSSTransition>
<button onClick={() => setShowMessage(true)}>
Show me
</button>
</Container>
);
}
ReactDOM.render(
<Example />,
document.getElementById('root')
);

How can I better animate a modal using CSS?

I am trying to use transitions to change the way a modal I created is shown on the screen. I need the modal to slide in from the left but the code doesn't seem to work. The app is in React.
The JSX code
const Modal = (props)=>{
let ModalClasses = [Styles.Modal];
if(props.show){
ModalClasses = [Styles.Modal, Styles.Open]
}
return(
<div className={ModalClasses.join(' ')}>
<div className={Styles.ModalNav} onClick={props.clicked}>
<div></div>
<div></div>
</div>
</div>
)
}
CSS
.Modal{
background-color: white;
position: absolute;
top: 0;
left: 25%;
width: 50vw;
height: 100vh;
z-index: 2;
transform: translateX(-100%);
transition: transform 500ms ease-out;
}
.Modal.Open{
transform: translateX(0);
}
.ModalNav div{
height: 3px;
width: 20px;
background-color: black;
margin: 5px;
position: absolute;
top: 10px;
right: 0;
cursor: pointer;
}
.ModalNav div:first-of-type{
transform: rotate(45deg);
}
.ModalNav div:last-of-type{
transform: rotate(-45deg);
}
JSX code for the component I am receiving props from
class App extends Component{
state={
showModal: false,
}
showModalHandler = ()=>{
this.setState({showModal:!this.state.showModal})
}
render(){
return (
<div className="App">
{ this.state.showModal ?
<Modal
show={this.state.showModal}
clicked={this.showModalHandler}/> : null}
{ this.state.showModal ?
<Backdrop
clicked={this.showModalHandler}/> : null}
<Table clicked={this.showModalHandler}/>
</div>
);
}
}
export default App;
My goal is to get the modal to slide in but it just pops right in.
https://salesruby.netlify.app/ can be viewed here.
Here's an alternative answer that is more in line with OP's original approach of using CSS transition instead of animating. This also let's you slide the menu back in so it slides both ways.
OP's problem is returning null in the App component if the modal is in a hidden state (which it starts with). So react will not render the modal, until you change the isOpen flag to true - at that point it renders directly in the open state (popping in), and the CSS transition has no effect. Subsequently when you close the modal, it is removed from the DOM, again without time for a transition so it "pops out".
The solution below is to render the modal in its starting state, and use the IsOpen flag to toggle the state of the element, rather than pulling it in and out of the DOM.
Here is a modified sandbox from Cyrus' answer that shows this - isModalOpen flag is used by the Modal to toggle the "open" class. The rest is basically the same as what OP started with.
https://codesandbox.io/s/bold-architecture-9t50w?file=/src/Components/Modal/Modal.js
App (Notice Modal is always returned, no : null - and isModalOpen is bound)
import React, { useState } from "react";
import "./styles.css";
import Modal from "./Components/Modal/Modal";
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setIsModalOpen(true)}>Open modal </button>
<Modal isModalOpen={isModalOpen} closeModal={setIsModalOpen} />
</div>
);
};
export default App;
Modal (Notice className={isModalOpen ? "open" : ""} to toggle class)
import React from "react";
import "./modal.css";
const Modal = ({ closeModal, isModalOpen }) => {
return (
<div id="modal" className={isModalOpen ? "open" : ""}>
Modal here <button onClick={() => closeModal(false)}> open modal</button>
</div>
);
};
export default Modal;
CSS (simple transform and transition effect)
#modal {
background-color: #000000cc;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
transform: translateX(-100%);
transition: transform 0.5s ease-in-out;
width: 30%;
height: 100%;
}
#modal.open {
transform: translateX(0);
}
so I made this really fast. This is just a demo on how to make the animation slide in from left to right. If you look at the css you can see that I have left : -600px and that is the width of the modal. You can make it dynamic so that it will only take some % of the screen, but don't forget to add a max-width.
here is a link to codesandbox
//app.js
import React, { useState } from "react";
import "./styles.css";
import Modal from "./Components/Modal/Modal";
const App = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<button onClick={() => setIsModalOpen(true)}>Open modal </button>
{isModalOpen ? <Modal closeModal={setIsModalOpen} /> : null}
</div>
);
};
export default App;
//Modal.js
import React, { useEffect } from "react";
import "./modal.css";
const Modal = ({ closeModal }) => {
return (
<div id="modal">
Modal here <button onClick={() => closeModal(false)}> open modal</button>
</div>
);
};
export default Modal;
//modal.css
#modal {
background-color: #000000cc;
position: absolute;
top: 0;
left: 100px;
right: 0;
bottom: 0;
display: flex;
justify-content: center;
align-items: center;
transition: top 2s;
animation: slideIn 1s;
width: 600px;
height: 600px;
}
#keyframes slideIn {
0% {
left: -600px;
}
100% {
left: 100px;
}
}

React CSSTransition Issues

I am trying to display header one in the beginning and then when clicked on, fade in the second header. However they both are showing up in beginning. My understanding is that the in prop is what controls when the children of get rendered. But for some reason they are being rendered in beginning when showTwo is false. Any help would be much appreciated.
Component
import React, { useState } from "react";
import { CSSTransition } from "react-transition-group";
import "./Intro.css";
function Intro() {
const [showOne, setShowOne] = useState(true);
const [showTwo, setShowTwo] = useState(false);
return (
<>
{showOne && <h1 onClick={() => setShowTwo(true)}>One!</h1>}
<CSSTransition
in={showTwo}
timeout={750}
onEnter={() => setShowOne(false)}
classNames="creation"
>
<h2>Two!</h2>
</CSSTransition>
</>
);
}
export default Intro;
CSS
.creation-enter {
opacity: 0;
}
.creation-enter-active {
opacity: 1;
transition: opacity 700ms;
}
.creation-exit {
opacity: 1;
}
.creation-exit-active {
opacity: 0;
transition: opacity 700ms;
}
I realized that I was missing the prop unmountOnExit and misunderstanding what the in prop is for.
If others stumble upon this with similar issues, in is used to determine when the *-enter, *-enter-active, ... are added as classes.

React CSSTransition Help. Animation resetting. and not setting the enter class right away

my React code
import React from 'react'
import ReactDOM from 'react-dom'
import { CSSTransition } from 'react-transition-group'
import './styles.css';
const Fade = ({ children, ...props }) => (
<CSSTransition
{...props}
timeout={600}
classNames="slide-filter"
>
{children}
</CSSTransition>
);
class FadeInAndOut extends React.Component {
state = {
show: false
}
render() {
return (
<div>
<button onClick={() => this.setState((prevState) => {
return { show: !prevState.show }
})}>Toggle</button>
<hr/>
<Fade in={this.state.show}>
<div className='greeting'>Hello world</div>
</Fade>
</div>
)
}
}
My CSS classes
.slide-filter-enter {
transform: translateX(50%);
opacity: 0;
transition: all 0.6s ease-in;
color:blue;
}
.slide-filter-enter-active {
transform: translateX(0);
color:red;
opacity: 1;
}
.slide-filter-exit {
transform: translateY(0);
opacity: 1;
color:red;
}
.slide-filter-exit-active {
color:blue;
transform: translateX(50%);
transition: all 0.6s ease-in;
}
Ok I can't understand why this is not working. It just resets to its original state after animating.
Also why is the "enter" or "enter-active" class not applied the first time it is rendered on the screen?
So you want the position of the "Fade" component to stay at the end position of the animation? Forgive me if this is not the behavior you are looking for but try removing the timeout={1000} prop on line 10.

Resources