React模态窗口关闭无平滑动画问题的修复方案咨询
React模态框关闭无动画问题修复
问题现象
模态框打开时平滑有过渡动画,但点击关闭或按ESC后直接瞬间消失,完全看不到关闭动画效果。
现有代码
Modal组件代码
const ModalContent = ({ children, closeModal }) => { useEffect(() => { const listenerHandler = (e) => { if (e.key === 'Escape') { closeModal() } } document.addEventListener('keydown', listenerHandler) return () => { document.removeEventListener('keydown', listenerHandler) } }, [closeModal]) return children } export function Modal({ showModal, handleClose, children, }) { useEffect(() => { showModal ? document.body.style.overflow = 'hidden' : document.body.style.overflow = 'unset' }, [showModal]) if (!showModal) return null const classes = classNames({ modal: true, hide: !showModal, }) return createPortal( <div className={classes} onClick={handleClose}> <div className="modalContent" onClick={(e) => e.stopPropagation()}> <ModalContent closeModal={handleClose}> {children} </ModalContent> </div> </div>, document.getElementById('modal-root'), ) }
CSS样式
.modal { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; z-index: 99; background-color: rgba(0, 0, 0, .9); display: flex; justify-content: center; align-items: center; animation-name: fade; animation-duration: .4s; } .modal.hide { animation-name: fade-out; animation-duration: .5s; } @keyframes fade { from { opacity: 0.7; } to { opacity: 1; } } @keyframes fade-out { from { opacity: 1; } to { opacity: 0; } }
使用模态框的组件代码
export function Project({ projectName, projectDescription, projectLinkToGitHub, arrayOfUsedTools, orientation, }) { const [currentImage, setCurrentImage] = useState(null) const [showModal, setShowModal] = useState(false) const imageClickHandler = (picture) => { setCurrentImage(picture) setShowModal(true) } const handleClose = () => setShowModal(false) return ( ...... <Swiper spaceBetween={30} centeredSlides autoplay={{ delay: 2000, disableOnInteraction: false,}} pagination={{ clickable: true,}} navigation modules={[Autoplay, Pagination, Navigation]} grabCursor loop slidesPerView={1} className={leftStyles.swiperContainer} > {dogFoodImages.map((picture, i) => ( <SwiperSlide key={i} className={leftStyles.swiperSlide} onClick={() => imageClickHandler(picture)} > <img src={picture} alt={picture} className={leftStyles.screens} /> </SwiperSlide> ...... <Modal showModal={showModal} handleClose={handleClose}> <img src={currentImage} alt="" className={rightStyles.screensForModal} /> </Modal> }
问题根源
核心问题是:当showModal变为false时,Modal组件直接return null,DOM被立即移除,关闭动画根本没机会执行。
修复方案
1. 修改Modal组件逻辑,延迟移除DOM
新增isClosing状态标记关闭过程,等动画执行完再移除DOM:
import { useEffect, useState, useRef } from 'react'; import { createPortal } from 'react-dom'; import classNames from 'classnames'; const ModalContent = ({ children, closeModal }) => { useEffect(() => { const listenerHandler = (e) => { if (e.key === 'Escape') { closeModal(); } }; document.addEventListener('keydown', listenerHandler); return () => { document.removeEventListener('keydown', listenerHandler); }; }, [closeModal]); return children; }; export function Modal({ showModal, handleClose, children }) { const [isClosing, setIsClosing] = useState(false); useEffect(() => { if (showModal) { document.body.style.overflow = 'hidden'; setIsClosing(false); } else { setIsClosing(true); // 等待关闭动画结束后恢复body滚动 const timer = setTimeout(() => { document.body.style.overflow = 'unset'; }, 500); // 时长要和fade-out动画的duration一致 return () => clearTimeout(timer); } }, [showModal]); // 只有当模态框既不显示也不在关闭过程中时,才不渲染DOM if (!showModal && !isClosing) return null; const classes = classNames({ modal: true, hide: !showModal, }); return createPortal( <div className={classes} onClick={handleClose} // 动画结束后重置isClosing,确保下次打开正常 onAnimationEnd={() => isClosing && setIsClosing(false)} > <div className="modalContent" onClick={(e) => e.stopPropagation()}> <ModalContent closeModal={handleClose}>{children}</ModalContent> </div> </div>, document.getElementById('modal-root') ); }
2. 优化CSS动画,确保结束状态生效
添加animation-fill-mode让动画结束后保持最终状态,同时在关闭动画结束后隐藏元素避免点击:
.modal { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; z-index: 99; background-color: rgba(0, 0, 0, .9); display: flex; justify-content: center; align-items: center; animation-name: fade; animation-duration: .4s; animation-fill-mode: forwards; /* 保持动画结束后的状态 */ } .modal.hide { animation-name: fade-out; animation-duration: .5s; animation-fill-mode: forwards; } @keyframes fade { from { opacity: 0.7; } to { opacity: 1; } } @keyframes fade-out { from { opacity: 1; visibility: visible; } to { opacity: 0; visibility: hidden; /* 动画结束后隐藏,防止点击穿透 */ } }
关键修复点
- 用
isClosing状态延迟DOM移除,给关闭动画留足执行时间 - 定时器时长必须和CSS中
fade-out的animation-duration一致,保证动画完整执行 animation-fill-mode: forwards确保动画结束后保持最终的透明度和可见性- 通过
onAnimationEnd事件在动画结束后重置状态,不影响下次打开
内容的提问来源于stack exchange,提问作者Ramil
相关产品推荐
相关产品推荐

