检测点击React组件外部
我正在寻找一种方法来检测单击事件是否发生在组件之外,如本文所述 。 jQuery nearest()用于查看click事件中的目标是否具有dom元素作为其父项之一。 如果匹配,click事件属于其中一个孩子,因此不被视为在组件之外。
所以在我的组件,我想附加一个点击处理程序窗口。 当处理程序触发时,我需要将目标与组件的dom子项进行比较。
点击事件包含像“path”这样的属性,它似乎包含事件已经走过的dompath。 我不知道要比较什么或者如何最好地遍历它,我想有人必须已经把它放在一个聪明的效用函数中…不是吗?
下面的解决scheme使用ES6,遵循绑定的最佳实践以及通过方法设置ref。
/** * Component that alerts if you click outside of it */ class OutsideAlerter extends Component { constructor(props) { super(props); this.setWrapperRef = this.setWrapperRef.bind(this); this.handleClickOutside = this.handleClickOutside.bind(this); } componentDidMount() { document.addEventListener('mousedown', this.handleClickOutside); } componentWillUnmount() { document.removeEventListener('mousedown', this.handleClickOutside); } /** * Set the wrapper ref */ setWrapperRef(node) { this.wrapperRef = node; } /** * Alert if clicked on outside of element */ handleClickOutside(event) { if (this.wrapperRef && !this.wrapperRef.contains(event.target)) { alert('You clicked outside of me!'); } } render() { return ( <div ref={this.setWrapperRef}> {this.props.children} </div> ); } } OutsideAlerter.propTypes = { children: React.PropTypes.element.isRequired }
下面是最适合我的解决scheme,无需将事件附加到容器上:
某些HTML元素可以具有所谓的“ 焦点 ”,例如input元素。 这些元素也将对模糊事件作出反应,当他们失去焦点时。
为了让任何元素具有焦点的能力,只要确保其tabindex属性设置为-1以外的任何其他。 在普通的HTML中,通过设置tabindex属性,但在React中,你必须使用tabIndex(注意大写字母I)。
你也可以通过使用element.setAttribute('tabindex',0)
这是我用它来做一个自定义的DropDown菜单。
var DropDownMenu = React.createClass({ getInitialState: function(){ return { expanded: false } }, expand: function(){ this.setState({expanded: true}); }, collapse: function(){ this.setState({expanded: false}); }, render: function(){ if(this.state.expanded){ var dropdown = ...; //the dropdown content } else { var dropdown = undefined; } return ( <div className="dropDownMenu" tabIndex="0" onBlur={ this.collapse } > <div className="currentValue" onClick={this.expand}> {this.props.displayValue} </div> {dropdown} </div> ); } });
在尝试了很多方法之后,我决定使用github.com/Pomax/react-onclickoutside,因为它有多完整。
我通过npm安装了模块,并将其导入到我的组件中:
import onClickOutside from 'react-onclickoutside'
然后,在我的组件类中,我定义了handleClickOutside
方法:
handleClickOutside = () => { console.log('onClickOutside() method called') }
当导出我的组件时,我把它包装在onClickOutside()
:
export default onClickOutside(NameOfComponent)
而已。
我find了一个解决scheme,感谢Ben Alpert在discuss.reactjs.org 。 build议的方法附加一个处理程序的文件,但事实certificate是有问题的。 点击我的树中的一个组件导致了重新显示,在更新时删除了点击元素。 因为React的rerender在文档主体处理程序被调用之前发生,所以元素没有被检测为“在树内”。
解决scheme是在应用程序根元素上添加处理程序。
主要:
window.__myapp_container = document.getElementById('app') React.render(<App/>, window.__myapp_container)
零件:
import { Component, PropTypes } from 'react'; import ReactDOM from 'react-dom'; export default class ClickListener extends Component { static propTypes = { children: PropTypes.node.isRequired, onClickOutside: PropTypes.func.isRequired } componentDidMount () { window.__myapp_container.addEventListener('click', this.handleDocumentClick) } componentWillUnmount () { window.__myapp_container.removeEventListener('click', this.handleDocumentClick) } /* using fat arrow to bind to instance */ handleDocumentClick = (evt) => { const area = ReactDOM.findDOMNode(this.refs.area); if (!area.contains(evt.target)) { this.props.onClickOutside(evt) } } render () { return ( <div ref='area'> {this.props.children} </div> ) } }
这里没有其他答案为我工作。 我试图隐藏一个模糊的popup窗口,但由于内容是绝对定位的,onBlur甚至在点击内部内容时也发射。
这是一个为我工作的方法:
// Inside the component: onBlur(event) { // currentTarget refers to this component. // relatedTarget refers to the element where the user clicked (or focused) which // triggered this event. // So in effect, this condition checks if the user clicked outside the component. if (!event.currentTarget.contains(event.relatedTarget)) { // do your thing. } },
希望这可以帮助。
我被困在同一个问题上。 这里的派对我有点晚了,但对我来说这是一个非常好的解决scheme。 希望这会对别人有所帮助。 您需要从react-dom
导入findDOMNode
componentDidMount() { document.addEventListener('click', this.handleClickOutside.bind(this), true); } componentWillUnmount() { document.removeEventListener('click', this.handleClickOutside.bind(this), true); } handleClickOutside(event) { const domNode = ReactDOM.findDOMNode(this); if (!domNode || !domNode.contains(event.target)) { this.setState({ visible : false }); } }
这是我的方法(演示 – https://jsfiddle.net/agymay93/4/ ):
我创build了一个名为WatchClickOutside
特殊组件,它可以像(我假设JSX
语法)一样使用:
<WatchClickOutside onClickOutside={this.handleClose}> <SomeDropdownEtc> </WatchClickOutside>
这里是WatchClickOutside
组件的代码:
import React, { Component } from 'react'; export default class WatchClickOutside extends Component { constructor(props) { super(props); this.handleClick = this.handleClick.bind(this); } componentWillMount() { document.body.addEventListener('click', this.handleClick); } componentWillUnmount() { // remember to remove all events to avoid memory leaks document.body.removeEventListener('click', this.handleClick); } handleClick(event) { const {container} = this.refs; // get container that we'll wait to be clicked outside const {onClickOutside} = this.props; // get click outside callback const {target} = event; // get direct click event target // if there is no proper callback - no point of checking if (typeof onClickOutside !== 'function') { return; } // if target is container - container was not clicked outside // if container contains clicked target - click was not outside of it if (target !== container && !container.contains(target)) { onClickOutside(event); // clicked outside - fire callback } } render() { return ( <div ref="container"> {this.props.children} </div> ); } }
我最关心的所有其他答案是必须过滤从根/父下来的点击事件。 我发现最简单的方法是简单地设置一个兄弟元素的位置:固定,下拉后面的z-index 1,并处理相同组件内的固定元素上的click事件。 保持一切集中到一个给定的组件。
示例代码
#HTML <div className="parent"> <div className={`dropdown ${this.state.open ? open : ''}`}> ...content </div> <div className="outer-handler" onClick={() => this.setState({open: false})}> </div> </div> #SASS .dropdown { display: none; position: absolute; top: 0px; left: 0px; z-index: 100; &.open { display: block; } } .outer-handler { position: fixed; top: 0; left: 0; right: 0; bottom: 0; opacity: 0; z-index: 99; display: none; &.open { display: block; } }
对于那些需要绝对定位的人来说,我select的一个简单的选项是添加一个封装器组件,该封装器组件被devise为用一个透明的背景覆盖整个页面。 然后你可以在这个元素上添加一个onClick来closures你的内部组件。
<div style={{ position: 'fixed', top: '0', right: '0', bottom: '0', left: '0', zIndex: '1000', }} onClick={() => handleOutsideClick()} > <Content style={{position: 'absolute'}}/> </div>
现在,如果您在内容上添加点击处理程序,该事件也将传播到上面的div,因此触发handlerOutsideClick。 如果这不是你想要的行为,只需停止你的处理程序的事件进展。
<Content style={{position: 'absolute'}} onClick={e => { e.stopPropagation(); desiredFunctionCall(); }}/>
`
一个战略的例子
我喜欢所提供的解决scheme,通过在组件周围创build一个包装来完成同样的任务。
由于这更多的是我对Strategy的一种行为,所以提出了以下几点。
我是React的新手,为了在用例中保存一些样板,我需要一些帮助
请检查并告诉我你的想法。
ClickOutsideBehavior
import ReactDOM from 'react-dom'; export default class ClickOutsideBehavior { constructor({component, appContainer, onClickOutside}) { // Can I extend the passed component's lifecycle events from here? this.component = component; this.appContainer = appContainer; this.onClickOutside = onClickOutside; } enable() { this.appContainer.addEventListener('click', this.handleDocumentClick); } disable() { this.appContainer.removeEventListener('click', this.handleDocumentClick); } handleDocumentClick = (event) => { const area = ReactDOM.findDOMNode(this.component); if (!area.contains(event.target)) { this.onClickOutside(event) } } }
样例用法
import React, {Component} from 'react'; import {APP_CONTAINER} from '../const'; import ClickOutsideBehavior from '../ClickOutsideBehavior'; export default class AddCardControl extends Component { constructor() { super(); this.state = { toggledOn: false, text: '' }; this.clickOutsideStrategy = new ClickOutsideBehavior({ component: this, appContainer: APP_CONTAINER, onClickOutside: () => this.toggleState(false) }); } componentDidMount () { this.setState({toggledOn: !!this.props.toggledOn}); this.clickOutsideStrategy.enable(); } componentWillUnmount () { this.clickOutsideStrategy.disable(); } toggleState(isOn) { this.setState({toggledOn: isOn}); } render() {...} }
笔记
我想存储传递的component
生命周期钩子和覆盖方法与此类似:
const baseDidMount = component.componentDidMount; component.componentDidMount = () => { this.enable(); baseDidMount.call(component) }
component
是传递给ClickOutsideBehavior
构造函数的组件。
这将从用户的行为中删除启用/禁用样板,但它看起来不是很好
你可以在这个元素上安装一个双击处理程序。 在这个元素的处理程序中,只需返回false以防止事件传播。 所以当双击发生时,如果它在元素上,它将被捕获,并不会传播到正文的处理程序。 否则它会被身体的处理程序抓住。
更新:如果你真的不想阻止事件传播,你只需要使用最近的来检查点击是否发生在你的元素或他的一个孩子:
<html> <head> <script src="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>