跨域场景下的Iframe事件监听

发布于:2025-08-09 ⋅ 阅读:(12) ⋅ 点赞:(0)

背景

在当前window窗口,对于一些浮窗组件,一般需要点击当前window下的其他位置才能够隐藏浮窗。但如果当前窗口中存在iframe区域,那么由于一些特殊的性质,无法通过常规的click点击事件监听iframe元素的点击,而通过contentDocument的形式也很难在跨域存在的情况下实现。

解决方案

window对象本身可以监听到blur方法,在点击到iframe时,会触发父级窗口的blur事件,此时可以间接监听到相当于点击到了iframe的事件。

缺陷

当然,缺陷很明显,其他方式触发了窗口的blur时,也会触发该事件,而不是点击到了iframe。但对于以上场景,是完全够用的。

代码

import { ref, onUnmounted, Ref } from 'vue';

export function useIframeClick(
  iframeRef: Ref<HTMLIFrameElement | undefined>,
  handleIframeClick: () => void
) {
  // 监听iframe点击的状态
  const iframeClickDetection = ref({
    isSetup: false,
    blurTimer: null as NodeJS.Timeout | null,
    focusTimer: null as NodeJS.Timeout | null,
  });

  // 覆盖层相关状态
  const showClickOverlay = ref(false);
  const overlayTimer = ref<NodeJS.Timeout | null>(null);

  // 处理覆盖层点击
  const handleOverlayClick = () => {
    console.log('覆盖层检测到点击');
    handleIframeClick();
    // 点击后短暂隐藏覆盖层,让用户能正常与iframe交互
    showClickOverlay.value = false;
    setTimeout(() => {
      showClickOverlay.value = true;
    }, 100);
  };

  // 鼠标进入覆盖层时暂时隐藏,允许正常交互
  const handleOverlayMouseEnter = () => {
    if (overlayTimer.value) {
      clearTimeout(overlayTimer.value);
    }
    overlayTimer.value = setTimeout(() => {
      showClickOverlay.value = false;
    }, 500); // 500ms后隐藏
  };

  // 鼠标离开后重新显示覆盖层
  const handleOverlayMouseLeave = () => {
    if (overlayTimer.value) {
      clearTimeout(overlayTimer.value);
    }
    overlayTimer.value = setTimeout(() => {
      showClickOverlay.value = true;
    }, 1000); // 1s后重新显示
  };

  // 设置iframe点击检测
  const setupIframeClickDetection = () => {
    if (iframeClickDetection.value.isSetup) return;

    const detection = iframeClickDetection.value;

    // 监听window的焦点事件
    const handleWindowBlur = () => {
      // 延迟检测,确保是iframe获得了焦点
      detection.blurTimer = setTimeout(() => {
        // 检查当前活动元素是否是iframe
        if (document.activeElement === iframeRef.value) {
          handleIframeClick();
        }
      }, 0);
    };

    const handleWindowFocus = () => {
      // 清除blur定时器
      if (detection.blurTimer) {
        clearTimeout(detection.blurTimer);
        detection.blurTimer = null;
      }
    };

    // 监听鼠标移动事件,确保是真实的点击而不是键盘导航
    let mouseMovedOverIframe = false;
    const handleMouseMove = (e: MouseEvent) => {
      const iframeRect = iframeRef.value?.getBoundingClientRect();
      if (iframeRect) {
        const isOverIframe =
          e.clientX >= iframeRect.left &&
          e.clientX <= iframeRect.right &&
          e.clientY >= iframeRect.top &&
          e.clientY <= iframeRect.bottom;
        mouseMovedOverIframe = isOverIframe;
      }
    };

    // 改进的blur处理
    const handleWindowBlurImproved = () => {
      detection.blurTimer = setTimeout(() => {
        if (document.activeElement === iframeRef.value && mouseMovedOverIframe) {
          handleIframeClick();
        }
      }, 0);
    };

    window.addEventListener('blur', handleWindowBlurImproved);
    window.addEventListener('focus', handleWindowFocus);
    document.addEventListener('mousemove', handleMouseMove);

    detection.isSetup = true;

    // 清理函数
    onUnmounted(() => {
      window.removeEventListener('blur', handleWindowBlurImproved);
      window.removeEventListener('focus', handleWindowFocus);
      document.removeEventListener('mousemove', handleMouseMove);
      if (detection.blurTimer) {
        clearTimeout(detection.blurTimer);
      }
    });
  };

  return {
    showClickOverlay,
    setupIframeClickDetection,
    handleOverlayClick,
    handleOverlayMouseEnter,
    handleOverlayMouseLeave,
  };
}


网站公告

今日签到

点亮在社区的每一天
去签到