我正在寻找一种方法来检测单击事件是否发生在组件之外,如本文所述。jQueryclosest()用于查看单击事件的目标是否将dom元素作为其父元素之一。如果存在匹配项,则单击事件属于其中一个子项,因此不被视为在组件之外。
因此,在我的组件中,我想将一个单击处理程序附加到窗口。当处理程序启动时,我需要将目标与组件的dom子级进行比较。
click事件包含类似“path”的财产,它似乎保存了事件经过的dom路径。我不知道该比较什么,或者如何最好地遍历它,我想肯定有人已经把它放在了一个聪明的效用函数中。。。不
import React, { useState, useEffect, useRef } from "react";
const YourComponent: React.FC<ComponentProps> = (props) => {
const ref = useRef<HTMLDivElement | null>(null);
const [myState, setMyState] = useState(false);
useEffect(() => {
const listener = (event: MouseEvent) => {
// we have to add some logic to decide whether or not a click event is inside of this editor
// if user clicks on inside the div we dont want to setState
// we add ref to div to figure out whether or not a user is clicking inside this div to determine whether or not event.target is inside the div
if (
ref.current &&
event.target &&
// contains is expect other: Node | null
ref.current.contains(event.target as Node)
) {
return;
}
// if we are outside
setMyState(false);
};
// anytime user clics anywhere on the dom, that click event will bubble up into our body element
// without { capture: true } it might not work
document.addEventListener("click", listener, { capture: true });
return () => {
document.removeEventListener("click", listener, { capture: true });
};
}, []);
return (
<div ref={ref}>
....
</div>
);
};
您只需在主体上安装一个双击处理程序,并在此元素上安装另一个。在该元素的处理程序中,只需返回false以防止事件传播。因此,当双击发生时,如果它在元素上,它将被捕获,并且不会传播到主体上的处理程序。否则它会被身体上的处理程序抓住。
更新:如果你真的不想阻止事件传播,你只需要使用closest来检查点击是发生在你的元素还是他的一个孩子身上:
<html>
<head>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<script>
$(document).on('click', function(event) {
if (!$(event.target).closest('#div3').length) {
alert("outside");
}
});
</script>
</head>
<body>
<div style="background-color:blue;width:100px;height:100px;" id="div1"></div>
<div style="background-color:red;width:100px;height:100px;" id="div2"></div>
<div style="background-color:green;width:100px;height:100px;" id="div3"></div>
<div style="background-color:yellow;width:100px;height:100px;" id="div4"></div>
<div style="background-color:grey;width:100px;height:100px;" id="div5"></div>
</body>
</html>
更新:不带jQuery:
<html>
<head>
<script>
function findClosest (element, fn) {
if (!element) return undefined;
return fn(element) ? element : findClosest(element.parentElement, fn);
}
document.addEventListener("click", function(event) {
var target = findClosest(event.target, function(el) {
return el.id == 'div3'
});
if (!target) {
alert("outside");
}
}, false);
</script>
</head>
<body>
<div style="background-color:blue;width:100px;height:100px;" id="div1"></div>
<div style="background-color:red;width:100px;height:100px;" id="div2"></div>
<div style="background-color:green;width:100px;height:100px;" id="div3">
<div style="background-color:pink;width:50px;height:50px;" id="div6"></div>
</div>
<div style="background-color:yellow;width:100px;height:100px;" id="div4"></div>
<div style="background-color:grey;width:100px;height:100px;" id="div5"></div>
</body>
</html>
我之所以这样做,部分原因是遵循了这一点,并遵循了React官方文件关于处理需要React ^16.3的参考文献。这是我在尝试了其他一些建议之后唯一有效的方法。。。
class App extends Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
componentWillMount() {
document.addEventListener("mousedown", this.handleClick, false);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClick, false);
}
handleClick = e => {
/*Validating click is made inside a component*/
if ( this.inputRef.current === e.target ) {
return;
}
this.handleclickOutside();
};
handleClickOutside(){
/*code to handle what to do when clicked outside*/
}
render(){
return(
<div>
<span ref={this.inputRef} />
</div>
)
}
}
我喜欢@Ben Bud的答案,但当有视觉上嵌套的元素时,contains(event.target)并不能像预期的那样工作。
因此,有时最好计算点击点是否位于元素内部。
这是我的React Hook代码。
import { useEffect } from 'react'
export function useOnClickRectOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
const targetEl = ref.current
if (targetEl) {
const clickedX = event.clientX
const clickedY = event.clientY
const rect = targetEl.getBoundingClientRect()
const targetElTop = rect.top
const targetElBottom = rect.top + rect.height
const targetElLeft = rect.left
const targetElRight = rect.left + rect.width
if (
// check X Coordinate
targetElLeft < clickedX &&
clickedX < targetElRight &&
// check Y Coordinate
targetElTop < clickedY &&
clickedY < targetElBottom
) {
return
}
// trigger event when the clickedX,Y is outside of the targetEl
handler(event)
}
}
document.addEventListener('mousedown', listener)
document.addEventListener('touchstart', listener)
return () => {
document.removeEventListener('mousedown', listener)
document.removeEventListener('touchstart', listener)
}
}, [ref, handler])
}
这已经有很多答案了,但它们没有解决e.stopPropagation()和阻止单击要关闭的元素之外的react链接的问题。
由于React有自己的人工事件处理程序,您无法将文档用作事件侦听器的基础。在这之前,您需要e.stopPropagation(),因为React使用文档本身。如果改用document.querySelector('body')。您可以防止点击React链接。下面是我如何实现单击外部并关闭的示例。这使用ES6和React 16.3。
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
};
this.insideContainer = React.createRef();
}
componentWillMount() {
document.querySelector('body').addEventListener("click", this.handleClick, false);
}
componentWillUnmount() {
document.querySelector('body').removeEventListener("click", this.handleClick, false);
}
handleClick(e) {
/* Check that we've clicked outside of the container and that it is open */
if (!this.insideContainer.current.contains(e.target) && this.state.isOpen === true) {
e.preventDefault();
e.stopPropagation();
this.setState({
isOpen: false,
})
}
};
togggleOpenHandler(e) {
e.preventDefault();
this.setState({
isOpen: !this.state.isOpen,
})
}
render(){
return(
<div>
<span ref={this.insideContainer}>
<a href="#open-container" onClick={(e) => this.togggleOpenHandler(e)}>Open me</a>
</span>
<a href="/" onClick({/* clickHandler */})>
Will not trigger a click when inside is open.
</a>
</div>
);
}
}
export default App;