如何为条件渲染的Tailwind CSS React模态框添加指定动画
解决React+Tailwind模态框条件渲染导致动画不生效的问题
问题根源
直接通过{isModalOpen && <Modal />}的条件渲染方式,会让模态框组件被立即挂载/卸载,没有留给Tailwind过渡动画执行的时间,因此动画无法正常触发。
解决方案
放弃直接条件卸载组件的逻辑,改用状态控制模态框的可见性类名,等退出动画完全结束后再真正卸载组件。具体修改分为两步:
1. 改造Modal组件,支持动画状态控制
新增isOpen属性控制显示/隐藏的类名,同时添加onAnimationEnd回调通知父组件动画完成:
import { useEffect, useRef } from 'react'; export const Modal = ({ isOpen, onClose, onAnimationEnd, children, title = "" }) => { const handleBackdropClick = (e) => { e.target === e.currentTarget && onClose(); }; const modalRef = useRef(null); // 控制body滚动状态 useEffect(() => { document.body.style.overflow = isOpen ? "hidden" : "unset"; }, [isOpen]); // 监听动画结束事件,通知父组件卸载 useEffect(() => { const handleEnd = () => { if (!isOpen) onAnimationEnd(); }; const element = modalRef.current; if (element) { element.addEventListener('animationend', handleEnd); return () => element.removeEventListener('animationend', handleEnd); } }, [isOpen, onAnimationEnd]); return ( <div ref={modalRef} className={`fixed inset-0 flex justify-center items-top py-2 px-2 z-50 transition-all ease-in-out delay-150 duration-300 ${ isOpen ? 'opacity-100 visible' : 'opacity-0 invisible pointer-events-none' }`} onClick={handleBackdropClick} > {/* 背景遮罩层 */} <div className="absolute inset-0 bg-slate-950 bg-opacity-50 transition-opacity ease-in-out delay-150 duration-300"></div> {/* 模态框主体 */} <div className={`relative bg-white border-2 p-4 md:p-6 shadow-xl rounded-3xl w-full md:w-min overflow-y-auto min-h-fit h-max transform transition-all ease-in-out delay-150 duration-300 ${ isOpen ? 'scale-100 opacity-100' : 'scale-90 opacity-0' }`}> <div className="flex items-center justify-between sticky top-0 gap-8 bg-white pb-2"> <h4 className="text-primary-text text-base md:text-xl font-bold w-max"> {title} </h4> <span className="cursor-pointer text-gray-500" onClick={onClose}> <interfaceIcons.CloseIcon size="24" /> </span> </div> {children} </div> </div> ); };
2. 修改HomePage的调用逻辑
新增两个状态:一个控制组件是否挂载,一个控制动画的显示/隐藏,确保动画有足够的执行时间:
import { useState } from 'react'; import { Modal } from './Modal'; export const HomePage= () => { const [isModalVisible, setIsModalVisible] = useState(false); const [isModalMounted, setIsModalMounted] = useState(false); // 打开模态框:先挂载组件,再触发显示动画 const handleModalOpen = () => { setIsModalMounted(true); setTimeout(() => setIsModalVisible(true), 10); // 给DOM渲染留极短时间,避免动画失效 }; // 关闭模态框:先触发退出动画 const handleModalClose = () => { setIsModalVisible(false); }; // 动画结束后卸载组件 const handleAnimationEnd = () => { setIsModalMounted(false); }; return ( <> <button onClick={handleModalOpen}>Open modal</button> {isModalMounted && ( <Modal isOpen={isModalVisible} title="My modal" onClose={handleModalClose} onAnimationEnd={handleAnimationEnd} > content here! </Modal> )} </> ); };
核心细节说明
- 用
isModalMounted控制组件是否存在于DOM中,isModalVisible控制动画类名切换,保证动画有完整的执行周期。 - 背景层和模态框主体都添加了
ease-in-out delay-150 duration-300类,实现同步的过渡效果。 - 关闭时先切换类名触发退出动画,动画结束后再通过回调卸载组件;打开时先挂载组件,延迟极短时间再触发显示动画,避免DOM未渲染完成导致动画不生效。
内容的提问来源于stack exchange,提问作者Muhammed Sahad
相关产品推荐
相关产品推荐

