You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用useEffect Hook实现鼠标移出窗口时模态框仅弹窗一次?

实现鼠标移出窗口仅弹出一次模态框

方案一:触发后立即移除事件监听

直接在第一次触发弹窗后移除mouseleave事件监听,后续鼠标移出操作就不会再触发弹窗:

React.useEffect(() => {
  const popupWhenLeave = (event) => {
    if (
      event.clientY <= 0 ||
      event.clientX <= 0 ||
      event.clientX >= window.innerWidth ||
      event.clientY >= window.innerHeight
    ) {
      handleClickOpen();
      // 触发弹窗后立即移除监听,避免重复触发
      document.removeEventListener("mouseleave", popupWhenLeave);
    }
  };
  document.addEventListener("mouseleave", popupWhenLeave);

  // 组件卸载时清理监听
  return () => {
    document.removeEventListener("mouseleave", popupWhenLeave);
  };
}, []);

方案二:使用useRef标记弹窗状态

如果需要保留监听(比如后续有重置需求),可以用useRef记录弹窗是否已触发,避免重复执行:

const hasTriggeredPopup = useRef(false);

React.useEffect(() => {
  const popupWhenLeave = (event) => {
    if (
      event.clientY <= 0 ||
      event.clientX <= 0 ||
      event.clientX >= window.innerWidth ||
      event.clientY >= window.innerHeight
    ) {
      // 仅当未触发过弹窗时执行
      if (!hasTriggeredPopup.current) {
        handleClickOpen();
        hasTriggeredPopup.current = true;
      }
    }
  };
  document.addEventListener("mouseleave", popupWhenLeave);

  return () => {
    document.removeEventListener("mouseleave", popupWhenLeave);
  };
}, []);

之前方案失效的原因

  • 第一段代码频繁触发:没有任何限制逻辑,每次mouseleave事件都会执行handleClickOpen。
  • 第二段代码加cursorOut状态但依赖为空数组:setCursorOut(true)会触发组件重新渲染,但useEffect依赖为空不会重新执行,事件监听依然存在,所以还是会重复触发。
  • 添加[cursorOut]依赖后弹窗不弹出:cursorOut变为true时,useEffect重新执行,此时if(!cursorOut)不成立不会添加新监听,同时返回函数会移除旧监听,导致后续无监听触发弹窗;且第一次触发时cursorOut的更新是异步的,可能还未完成就再次触发事件。

内容的提问来源于stack exchange,提问作者Krishna Thorat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 04:55:29