React onMouseEnter/onMouseLeave悬停PopUp闪烁循环问题排查
问题产生原因
闪烁是两个问题叠加导致的:
- 你渲染的PopUp最外层用了
fixed inset-0的全屏定位,还额外加了一个inset-0 z-40的全屏遮罩层,这两个元素渲染出来之后会直接覆盖在原本的按钮上方。鼠标悬停在按钮上触发PopUp渲染的瞬间,鼠标的悬停对象就从按钮变成了盖在上面的PopUp/遮罩层,按钮直接触发onMouseLeave把PopUp关掉;PopUp消失后鼠标又落回按钮上,再次触发onMouseEnter渲染PopUp,循环往复就出现了持续闪烁的现象。 - 定时器逻辑存在bug:一是你把定时器ID存在普通变量
TooltipTimeout里,组件每次重渲染都会重置这个变量,根本拿不到之前的定时器ID;二是你用clearInterval去清除setTimeout创建的定时器,两者API不匹配,根本清不掉,会出现残留定时器意外修改状态的问题。
修复方案
按以下步骤调整即可:
- 解决遮罩层抢鼠标事件的问题,两种方案选其一即可:
- 做居中全屏模态式弹窗:给全屏遮罩层和PopUp外层的fixed容器加上
pointer-events-none类,让鼠标事件可以穿透遮罩层,不会打断按钮的悬停状态;PopUp的内容容器再加pointer-events-auto,保证弹窗内的链接、按钮可以正常点击。 - 做跟随按钮的小提示Tooltip:把父容器设为
relative,PopUp改成absolute定位放在按钮旁边,同时把onMouseEnter和onMouseLeave绑定到这个共同父容器上,鼠标在按钮或者PopUp上时都不会触发关闭,交互更自然。
- 做居中全屏模态式弹窗:给全屏遮罩层和PopUp外层的fixed容器加上
- 修正定时器逻辑:用
useRef存储定时器ID,保证重渲染时ID不会丢失;清除定时器时统一用clearTimeout匹配setTimeout,同时在组件卸载时补充清除逻辑,避免内存泄漏。
修复后完整代码
import { useState, useRef, useEffect } from 'react'; import { TooltipClose } from "../../../utils/svg"; export default function CustomTooltipsRed() { const [activeToolTip, setActiveToolTip] = useState(false); // 用useRef存定时器ID,重渲染不丢失 const TooltipTimeout = useRef(null); const showToolTip = () => { // 先清掉之前的定时器,避免重复触发 if (TooltipTimeout.current) clearTimeout(TooltipTimeout.current); TooltipTimeout.current = setTimeout(() => { setActiveToolTip(true); }, 50); }; const hideToolTip = () => { setActiveToolTip(false); // 用clearTimeout清除setTimeout if (TooltipTimeout.current) { clearTimeout(TooltipTimeout.current); TooltipTimeout.current = null; } }; // 组件卸载时清定时器 useEffect(() => { return () => { if (TooltipTimeout.current) clearTimeout(TooltipTimeout.current); } }, []); return ( <div className="flex items-center justify-center "> <button className="bg-pink-500 text-white font-bold uppercase text-sm px-6 py-3 rounded shadow mr-1 mb-1 " onMouseEnter={() => showToolTip()} onMouseLeave={() => hideToolTip()} > Hover button </button> {activeToolTip ? ( <> <div className="justify-center items-center flex fixed inset-0 outline-none focus:outline-none pointer-events-none"> <div className="relative w-auto my-6 mx-auto max-w-xl pointer-events-auto"> {/*content*/} <div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-[#F43F5E] outline-none"> {/*header*/} <div className="flex items-center justify-start p-2 rounded-t"> <div className="mr-2"> <i><TooltipClose /></i> </div> <h3 className="text-base font-semibold text-white"> P1 - Priority Issue </h3> </div> {/*body*/} <div className="relative p-6 flex-auto bg-[#1E293B] rounded-b-lg"> <p className="text-[#E2E8F0] leading-relaxed"> We have detected that your tenant has legacy protocols enabled. Why is this an issue? Legacy protocols can not enforce multi factor authentication and are considered a security risk. </p> <a href="/" className="text-[#E2E8F0] text-xs">Read More</a> </div> </div> </div> </div> {/* 遮罩层加pointer-events-none,不拦截鼠标事件 */} <div className="inset-0 z-40 pointer-events-none"></div> </> ) : null} </div > ); }
内容的提问来源于stack exchange,提问作者JustinVarela
相关产品推荐
相关产品推荐

