React useClickOutside钩子点击SVG元素触发弹窗关闭问题排查
问题根本原因
你实现的useClickOutside钩子中存在类型判断逻辑错误:
你在判断点击目标是否属于弹窗内部时,加了event.target instanceof HTMLElement的限制,但所有SVG元素都继承自SVGElement接口,不属于HTMLElement的实例,所以点击弹窗内的SVG元素时,这个条件会直接返回false,后续的ref.current.contains(event.target)校验逻辑完全不会执行,代码直接判定为点击了弹窗外部,触发关闭逻辑。
为什么添加z-index: -1或pointer-events: none可以临时修复问题
这两个属性都会让点击事件不会命中SVG元素本身,而是直接穿透到SVG下方的普通HTML元素(比如包裹SVG的<div>、<button>等),此时event.target变成了符合HTMLElement类型的普通元素,原有逻辑就能正常校验是否属于弹窗内部。
修复方案
直接修改钩子中的类型判断逻辑,把类型校验范围扩大到所有DOM节点(Node类型,HTMLElement和SVGElement都是Node的子类)即可,修改后的核心代码如下:
import React, { useEffect } from "react"; const useClickOutside = ( refs: Array<React.MutableRefObject<HTMLElement | null>>, handler: (event: any) => any ) => { useEffect(() => { const listener = (event: MouseEvent | TouchEvent) => { for (let ref of refs) { // 把HTMLElement判断改为Node,兼容SVG等所有DOM节点 if (event.target instanceof Node && (!ref.current || ref.current.contains(event.target))) { return; } } handler(event); }; document.addEventListener("mousedown", listener); document.addEventListener("touchstart", listener); return () => { document.removeEventListener("mousedown", listener); document.removeEventListener("touchstart", listener); }; }, [refs, handler]); }; export default useClickOutside;
如果需要更严格的类型限制,也可以改成event.target instanceof HTMLElement || event.target instanceof SVGElement,效果一致。
内容的提问来源于stack exchange,提问作者Iliaaaa
相关产品推荐
相关产品推荐

