背景
在当前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,
};
}