Home > Enterprise >  Using React, how can I make the modal do an animated appear, without already being in the DOM?
Using React, how can I make the modal do an animated appear, without already being in the DOM?

Time:04-07

I have a modal which appears on the press of a button and is animated as it is displayed.

This works fine, BUT only if the modal code is already in the DOM when the button is pressed.

You can see the example here: https://codesandbox.io/s/loving-dan-7fwrkr

This is the problem: if the button adds the modal code to the DOM then the modal simply appears with no animation.

I've spent many hours trying various ways to make this work and the best I can come up with is to use window.setTimeout to trigger the animation 200 milliseconds after the modal code is added to the DOM. I don't like such a solution because it seems like a hack - I don't have any clear understanding why such a hack would work.

The example below demonstrates both cases.

Without the commented code, the animation works.

With the commented code, the modal simply appears without animation.

If anyone has any idea how to fix this it would be much appreciated.

My specific goal is to NOT have the modal code in the DOM prior to pressing a button to make it appear.

I've worked pretty hard to make the minimum possible example below, but it is still fairly large I apologise. If you have suggesting for cutting it further whilst still being relevant please let me know.

import ReactDOM from 'react-dom';
import React, {useState} from 'react';

const theStyle = `

    .md-modal {
        position: fixed;
        top: 50%;
        left: 50%;
        width: 50%;
        height: auto;
        z-index: 2000;
        visibility: hidden;
        transform: translateX(-50%) translateY(-50%);
    }
    
    .md-show {
        visibility: visible;
    }
    
    .md-overlay {
        position: fixed;
        width: 100%;
        height: 100%;
        visibility: hidden;
        top: 0;
        left: 0;
        z-index: 1000;
        opacity: 0;
        background: rgba(143, 27, 15, 0.8);
        transition: all 0.3s;
    }
    
    .md-show ~ .md-overlay {
        opacity: 1;
        visibility: visible;
    }
    
    .md-content {
        color: #fff;
        background: #e74c3c;
        position: relative;
        border-radius: 3px;
        margin: 0 auto;
    }
    
    .md-content h3 {
        opacity: 0.8;
    }
    
    .md-effect-1 .md-content {
        transform: scale(0.7);
        opacity: 0;
        transition: all 0.3s;
    }
    
    .md-show.md-effect-1 .md-content {
        transform: scale(1);
        opacity: 1;
    }

`

function App() {
    const [getVisible, setVisible] = useState(false);

    /*
    THE MODAL APPEAR ANIMATION DOES NOT WORK WHEN THIS IS UNCOMMENTED

    if (!getVisible) {
        return (
            <button onClick={() => setVisible(true)}>
                show modal
            </button>)
    }
    */

    return (
        <>
            <style>
                {theStyle}
            </style>
            <div className={`md-modal md-effect-1 ${(getVisible) && "md-show"}`}>
                <div className="md-content">
                    This is a modal window.<br/>
                    <button onClick={() => setVisible(false)} className="md-close">close</button>
                </div>
            </div>
            <div onClick={() => setVisible(false)} className="md-overlay"/>

            <button onClick={() => setVisible(true)} className="md-trigger">
                show modal
            </button>
        </>
    );
}

ReactDOM.render(<App/>, document.getElementById('root'));

CodePudding user response:

I've had similar issues, the reason was that the transition does not trigger if the modal immediately gets the end value of being visible when you add it to the DOM.

I solved it by putting the transition into an @keyframes animation. Then, after adding the modal to the DOM, you use classList.add() to trigger the animation.

Something like this

.modal {
   opacity:0
}

.animated {
   animation: showModal 1s forwards easeOut
}

@keyframes showModal {
    from {
      opacity: 0;
    }

     to {
      opacity: 1;
    }
}

JS after the modal is added to the DOM:

myModel.classList.add("animated")

CodePudding user response:

Self answer to my future self.

With the answer of @Kokodoko as my start point, I gained a better understanding of how animation works in CSS/JS and rewrote my modal entirely so it now does what I want.

Here's the code:

import ReactDOM from 'react-dom';
import React, {useState} from 'react';

const theStyle = `
    
    .animated {
       animation: showModal .2s forwards 
    }
    
    @keyframes showModal {
        from {
          opacity: 0;
          transform: scale(0.7);
        }
    
         to {
          opacity: 1;
          transform: scale(1);
        }
    }

    .modalOverlay {
        z-index: 1500;
        background: rgba(40,91,218,0.5); /* you must use this and not opacity because opacity changes the front color */
        position: fixed;
        width: 100%;
        height: 100%;
        top: 0;
        left: 0;
        margin: 0;
        padding: 0;
        display: flex;
        flex-direction: row;
        flex-wrap: nowrap;
        justify-content: center;
        align-content: stretch;
        align-items: center;
        }
    
    .modalContainer {
        z-index: 1600;
        order: 0;
        flex: 0 1 auto;
        align-self: auto;
        }

    #modalContent {
        z-index: 1700;
        opacity: 0;
        color: #fff;
        width: 500px;
        height: 200px;
        background: #e74c3c;
        position: relative;
        border-radius: 3px;
        margin: 0 auto;
    }
    
`

function Button() {
    const [getVisible, setVisible] = useState(false);

    return (
        <div>
            <button onClick={() => setVisible(true)}>
                show modal
            </button>
            {(getVisible) && <Modal setVisible={setVisible}/>}
        </div>
    )
}

function Modal({setVisible}) {

    React.useEffect(
        //() => window.setTimeout(document.getElementById("modalContent").classList.add("animated"))
        () => document.getElementById("modalContent").classList.add("animated")
        , [])

    const handleClickOnOverlay = (e) => {
        // clicks on the are sent through to the background so we must prevent that
        e.stopPropagation()
        setVisible(false)
    }

    const handleClickOnContainer = (e) => {
        // clicks on the modal are sent through to the background so we must prevent that
        e.stopPropagation()
    }

    const handleClickOnModal = (e) => {
        console.log('clicked on modal')
    }

    return (
        <>
            <style>
                {theStyle}
            </style>
            <div onClick={handleClickOnOverlay} className="modalOverlay">
                <div className={`modalContainer`} onClick={handleClickOnContainer}>
                    <div id="modalContent" onClick={handleClickOnModal}>
                        This is a modal window.<br/>
                        <button onClick={() => setVisible(false)} className="md-close">close</button>
                    </div>
                </div>
            </div>
        </>
    );
}

ReactDOM.render(<Button/>, document.getElementById('root'));
  • Related