使用 React,我怎样才能让模态动画出现,而不是已经在 DOM 中?

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

我有一个模式,它会在按下按钮时出现,并在显示时显示动画。

这工作正常,但只有当按下按钮时模态代码已经在 DOM 中。

您可以在此处查看示例: https://codesandbox.io/s/loving-dan-7fwrkr

这就是问题所在:如果按钮将模态代码添加到 DOM,那么模态只会出现而没有动画。

我花了很多时间尝试各种方法来完成这项工作,我能想到的最好的方法是使用 window.setTimeout 在模态代码添加到 [= 后 200 毫秒触发动画38=]。我不喜欢这样的解决方案,因为它看起来像是一个 hack - 我不清楚为什么这样的 hack 会起作用。

下面的例子展示了这两种情况。

没有注释的代码,动画有效。

使用注释代码,模态仅显示而没有动画。

如果有人知道如何解决这个问题,我们将不胜感激。

我的具体目标是在按下按钮使其出现之前 DOM 中不包含模态代码。

我已经非常努力地制作了下面可能的最小示例,但它仍然相当大,我深表歉意。如果您建议在仍然相关的情况下进一步削减它,请告诉我。

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'));

我遇到过类似的问题,原因是当您将模态添加到 DOM 时,如果模态立即获得可见的 end valuetransition 不会触发.

我通过将过渡放入 @keyframes 动画中解决了这个问题。然后,将模态添加到 DOM 后,使用 classList.add() 触发动画。

像这样

.modal {
   opacity:0
}

.animated {
   animation: showModal 1s forwards easeOut
}

@keyframes showModal {
    from {
      opacity: 0;
    }

     to {
      opacity: 1;
    }
}

模态添加后的JS DOM:

myModel.classList.add("animated")

对未来的自己的自我回答。

以@Kokodoko 的回答作为我的起点,我更好地理解了动画在 CSS/JS 中的工作原理,并完全重写了我的模态,现在它可以做我想要的了。

代码如下:

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'));