ClickOutSide 的解决方案

724 阅读2分钟

ClickOutSide 的解决方案

写组件经常会遇到这样的需求,比如 Dialog 弹窗和 Menu 导航菜单有时需要在点击组件外的其他区域后关闭。Ant Design 里 Select 组件在点击下拉框外的区域后也会折叠下拉框。具体来说就是需要在点击组件外的区域后执行某个操作。

image-20220725135922110

上面这种的实现方式是设置一层全局的覆盖整个显示区域的遮罩。再提高导航栏的 z-index 使它在遮罩的上层。当点击导航栏外的遮罩区域时关闭导航栏。导航栏这种页面上只会出现一个的组件使用这种方式来解决 ClickOutSide 的问题并没有什么问题。但是类似上面说的类似 Ant Design 里 Select Dialog Popover 这类组件如果也采用这种方式,z-index 将会很难管理。

所以类似 Ant Design 这样的组件库其实都是在 document 上监听鼠标的点击事件来判断点击的区域是否在组件区域外的。但是这种方式也会产生一些问题,比如页面上有内嵌的 iframe。点击 iframe 里的内容时,很明显 document 是监听不到的。

// 下面是我实际封装的 react hook 实现
import {MutableRefObject, useEffect, useRef} from "react";

type fn<T> = (callback:(event:MouseEvent)=>void)=>MutableRefObject<T>

export const useOutSideClick:fn<any> = (callback) => {
    const ref = useRef<any>()
    useEffect(() => {
        const handleClick = (event: MouseEvent) => {
            if (ref.current && !ref.current.contains(event.target as Node)) {
                callback(event)
            }
        }
        document.addEventListener('click', handleClick)
        return () => {
            document.removeEventListener('click', handleClick)
        }
    })
    return ref;
}

总结

解决 ClickOutSide 问题的两种方式

  • 使用全局遮罩,在遮罩层绑定点击事件。缺点是 z-index 不好管理。
  • 在 document 上监听点击事件判断点击的区域是否在组件内。缺点是无法处理 iframe 这样的特殊场景。

参考