Я ищу способ определить, произошло ли событие клика вне компонента, как описано в этой статье . jQuery closeest() используется, чтобы увидеть, имеет ли цель из события click элемент dom как один из его родителей. Если есть совпадение, событие click принадлежит одному из дочерних элементов и, следовательно, не считается вне его.
Итак, в моем компоненте я хочу привязать обработчик кликов к окну. Когда срабатывает обработчик, мне нужно сравнить цель с дочерними элементами моего компонента.
Событие клика содержит свойства, такие как "путь", который, как представляется, содержит путь dom, по которому прошло событие. Я не уверен, что сравнивать или как лучше всего пройти его, и я думаю, что кто-то, должно быть, уже положил это на умную функцию полезности... Нет?
Следующее решение использует ES6 и следует рекомендациям для привязки, а также для установки ссылки с помощью метода.
Чтобы увидеть это в действии: Code Sandbox demo
import React, { Component } from 'react';
import PropTypes from 'prop-types';
/**
* Component that alerts if you click outside of it
*/
export default 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: PropTypes.element.isRequired,
};
Вот решение, которое лучше всего сработало для меня без добавления событий в контейнер:
Некоторые элементы HTML могут иметь то, что известно как " фокус", например элементы ввода. Эти элементы также будут реагировать на событие размытия, когда они потеряют этот фокус.
Чтобы дать любому элементу возможность сосредоточиться, просто убедитесь, что для его атрибута tabindex установлено значение, отличное от -1. В обычном HTML, который был бы, установив атрибут tabindex, но в React вы должны использовать tabIndex (обратите внимание на капитал I).
Вы также можете сделать это с помощью JavaScript с помощью 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)
Что это.
tabIndex
/ onBlur
предложенным Пабло ? Как работает реализация и как ее поведение отличается от подхода onBlur
?
Я застрял на том же вопросе. Я немного опоздал на вечеринку, но для меня это действительно хорошее решение. Надеюсь, это поможет кому-то еще. Вам нужно импортировать findDOMNode
из react-dom
import ReactDOM from 'react-dom';
// ...
componentDidMount() {
document.addEventListener('click', this.handleClickOutside, true);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside, true);
}
handleClickOutside = event => {
const domNode = ReactDOM.findDOMNode(this);
if (!domNode || !domNode.contains(event.target)) {
this.setState({
visible: false
});
}
}
Вы можете создать многоразовый хук с именем useComponentVisible
.
import { useState, useEffect, useRef } from 'react';
export default function useComponentVisible(initialIsVisible) {
const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
const ref = useRef(null);
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsComponentVisible(false);
}
};
useEffect(() => {
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
});
return { ref, isComponentVisible, setIsComponentVisible };
}
Затем в компонент вы хотите добавить функциональность, чтобы сделать следующее:
const DropDown = () => {
const { ref, isComponentVisible } = useComponentVisible(true);
return (
<div ref={ref}>
{isComponentVisible && (<p>Dropdown Component</p>)}
</div>
);
}
Я нашел решение благодаря Бен Альперту на discuss.reactjs.org. Предлагаемый подход придает обработчик документа, но это оказалось проблематичным. Нажав на один из компонентов в моем дереве, вы получили редердер, который удалил элемент щелчка при обновлении. Поскольку reerender из React происходит до вызова обработчика тела документа, элемент не был обнаружен как "внутри" дерева.
Решением этого было добавить обработчик в корневой элемент приложения.
главная:
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>
)
}
}
document
нет?
Ни один из других ответов здесь не работал. Я пытался скрыть всплывающее окно, но поскольку содержимое было абсолютно позиционировано, 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.
}
},
Надеюсь, что это поможет.
[Обновление] Решение с React ^ 16.8 с использованием хуков
import React, { useEffect, useRef, useState } from 'react';
const SampleComponent = () => {
const [clickedOutside, setClickedOutside] = useState(false);
const myRef = useRef();
const handleClickOutside = e => {
if (!myRef.current.contains(e.target)) {
setClickedOutside(true);
}
};
const handleClickInside = () => setClickedOutside(false);
useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
});
return (
<button ref={myRef} onClick={handleClickInside}>
{clickedOutside ? 'Bye!' : 'Hello!'}
</button>
);
};
export default SampleComponent;
Решение с React ^ 16.3:
import React, { Component } from "react";
class SampleComponent extends Component {
state = {
clickedOutside: false
};
componentDidMount() {
document.addEventListener("mousedown", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("mousedown", this.handleClickOutside);
}
myRef = React.createRef();
handleClickOutside = e => {
if (!this.myRef.current.contains(e.target)) {
this.setState({ clickedOutside: true });
}
};
handleClickInside = () => this.setState({ clickedOutside: false });
render() {
return (
<button ref={this.myRef} onClick={this.handleClickInside}>
{this.state.clickedOutside ? "Bye!" : "Hello!"}
</button>
);
}
}
export default SampleComponent;
.contains is not a function
, это может быть связано с тем, что вы передаете ref prop пользовательскому компоненту, а не реальному элементу DOM, например <div>
Вот мой подход (demo - https://jsfiddle.net/agymay93/4/):
Я создал специальный компонент под названием 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>
);
}
}
"Внутренний" компонент
function OutsideClickComp() {
const innerRef = useRef(null);
useOuterClickNotifier(
// if you want to optimize performance a bit,
// don't provide an anonymous function here
// See link down under (*1)
e => alert("clicked outside of this component!"),
innerRef
);
return (
<div ref={innerRef}>
inside component
</div>
);
}
Таможенный Крюк
function useOuterClickNotifier(onOuterClick, innerRef) {
useLayoutEffect(
() => {
window.addEventListener("click", handleClick);
// called for each previous layout effect
return () => document.removeEventListener("click", handleClick);
function handleClick(e) {
!innerRef.current.contains(e.target) && onOuterClick(e);
}
},
[onOuterClick, innerRef] // invoke again, if inputs have changed
);
}
* 1 совет: оптимизация производительности за счет пропуска эффектов
Вот мое решение: мне пришлось реализовать какое-то всплывающее меню, которое закрывается автоматически при нажатии вне контейнера меню. Используя хуки, IMO дает следующие преимущества:
useOuterClickNotifier
везде Затем, в зависимости от вашего варианта использования, вы можете сделать что-нибудь во внешнем обратном вызове клика, который здесь для простоты обозначен alert("clicked outside of this component!")
. Например, установите некоторое состояние с useState
ловушки useState
или вызовите функцию обратного вызова (в моем случае), чтобы открыть/закрыть всплывающее меню.
Чтобы гарантировать, что прослушиватель внешнего клика события запускается после того, как React полностью обработал свои изменения и избежал проблем с параллелизмом (в моем случае useLayoutEffect
), вместо useEffect
и window.addEventListener
используется window.addEventListener
вместо document.addEventListener
.
Фон: useEffect
запускается асинхронно после фазы рендеринга, тогда как useLayoutEffect
вызывается синхронно после всех мутаций DOM (поведение жизненного цикла useEffect
отличается от componentDidMount
и componentDidUpdate
!). Что касается событий DOM, React регистрирует одного слушателя в document
. Зарегистрировав наш пользовательский прослушиватель событий для внешних щелчков в window
один уровень выше, этот слушатель гарантированно запустится после обработки реагирующего события в фазе пузырьков DOM. Таким образом, мы можем обеспечить следующую последовательность процессов:
react event handler -> react render -> outer click listener outside react scope
Смотрите также тайминги эффекта и useLayoutEffect.
Надеюсь, это поможет.
На это уже есть много ответов, но они не обращаются к e.stopPropagation()
и не позволяют щелкать по ссылкам реакции вне элемента, который вы хотите закрыть.
Из-за того, что в React имеется собственный искусственный обработчик событий, вы не можете использовать документ в качестве базы для прослушивателей событий. Перед этим вам нужно использовать e.stopPropagation()
поскольку React использует сам документ. Если вы используете, например, document.querySelector('body')
вместо этого. Вы можете предотвратить переход по ссылке Реакт. Ниже приведен пример того, как я реализую щелчок снаружи и близко.
Это использует 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;
Чтобы расширить принятый ответ, сделанный Беном Бадом, если вы используете styled-компоненты, передача ссылок таким образом приведет к ошибке, такой как "this.wrapperRef.contains не является функцией".
Предлагаемое исправление, в комментариях, чтобы обернуть стилизованный компонент с помощью div и передать туда ссылку, работает. Сказав это, в своих документах они уже объясняют причину этого и правильное использование ссылок внутри styled-components:
Передача ref prop к стилевому компоненту даст вам экземпляр оболочки StyledComponent, но не к базовому узлу DOM. Это связано с тем, как работают рефери. Невозможно напрямую вызывать методы DOM, такие как focus, для наших оболочек. Чтобы получить ссылку на фактический, обернутый DOM-узел, вместо этого передайте обратный вызов в службу innerRef.
Вот так:
<StyledDiv innerRef={el => { this.el = el }} />
Затем вы можете получить к нему доступ непосредственно в функции handleClickOutside:
handleClickOutside = e => {
if (this.el && !this.el.contains(e.target)) {
console.log('clicked outside')
}
}
Это также относится к подходу "onBlur":
componentDidMount(){
this.el.focus()
}
blurHandler = () => {
console.log('clicked outside')
}
render(){
return(
<StyledDiv
onBlur={this.blurHandler}
tabIndex="0"
innerRef={el => { this.el = el }}
/>
)
}
Я использовал этот модуль (я не связан с автором)
npm install react-onclickout --save
const ClickOutHandler = require('react-onclickout');
class ExampleComponent extends React.Component {
onClickOut(e) {
if (hasClass(e.target, 'ignore-me')) return;
alert('user clicked outside of the component!');
}
render() {
return (
<ClickOutHandler onClickOut={this.onClickOut}>
<div>Click outside of me!</div>
</ClickOutHandler>
);
}
}
Это хорошо сработало.
"Support for the experimental syntax 'classProperties' isn't currently enabled"
это просто работало сразу ». Для любого, кто пробует это решение, не забудьте стереть какой-либо устаревший код, который вы, возможно, скопировали при попытке попробовать другие решения. например, добавление прослушивателей событий кажется конфликтующим с этим.
componentWillMount(){
document.addEventListener('mousedown', this.handleClickOutside)
}
handleClickOutside(event) {
if(event.path[0].id !== 'your-button'){
this.setState({showWhatever: false})
}
}
Событие path[0]
- последний элемент, нажатый
Для тех, кто нуждается в абсолютном позиционировании, простым вариантом, который я выбрал, является добавление компонента-оболочки, который предназначен для покрытия всей страницы прозрачным фоном. Затем вы можете добавить onClick в этот элемент, чтобы закрыть свой внутренний компонент.
<div style={{
position: 'fixed',
top: '0', right: '0', bottom: '0', left: '0',
zIndex: '1000',
}} onClick={() => handleOutsideClick()} >
<Content style={{position: 'absolute'}}/>
</div>
Как сейчас, если вы добавите обработчик кликов по контенту, событие также будет распространено на верхний div и, следовательно, будет запускать обработчикOutsideClick. Если это не ваше желаемое поведение, просто остановите прогенцию событий на вашем обработчике.
<Content style={{position: 'absolute'}} onClick={e => {
e.stopPropagation();
desiredFunctionCall();
}}/>
`
Я сделал это частично, следуя этому и следуя официальным документам 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 => {
if (this.inputRef.current === e.target) {
return;
}
this.handleclickOutside();
};
handleClickOutside(){
...***code to handle what to do when clicked outside***...
}
render(){
return(
<div>
...***code for what outside***...
<span ref={this.inputRef}>
...***code for what "inside"***...
</span>
...***code for what outside***
)}}
Мне нравятся предоставленные решения, которые используют одно и то же, создавая оболочку вокруг компонента.
Поскольку это больше похоже на поведение, которое я думал о стратегии, и придумал следующее.
Я новичок в React, и мне нужна помощь, чтобы сохранить какой-либо шаблон в используемых случаях.
Пожалуйста, просмотрите и скажите мне, что вы думаете.
import ReactDOM from 'react-dom';
export default class ClickOutsideBehavior {
constructor({component, appContainer, onClickOutside}) {
// Can I extend the passed component 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
.
Это приведет к удалению шаблона enable/disable от пользователя этого поведения, но это выглядит не очень хорошо, хотя
Мое самое большое беспокойство во всех остальных ответах - это отфильтровать события щелчка от корня/родителя вниз. Я нашел, что самый простой способ - просто установить элемент sibling с позицией: fixed, 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;
}
}
import ReactDOM from 'react-dom' ;
class SomeComponent {
constructor(props) {
// First, add this to your constructor
this.handleClickOutside = this.handleClickOutside.bind(this);
}
componentWillMount() {
document.addEventListener('mousedown', this.handleClickOutside, false);
}
// Unbind event on unmount to prevent leaks
componentWillUnmount() {
window.removeEventListener('mousedown', this.handleClickOutside, false);
}
handleClickOutside(event) {
if(!ReactDOM.findDOMNode(this).contains(event.path[0])){
console.log("OUTSIDE");
}
}
}
import ReactDOM from 'react-dom';
Чтобы заставить решение "focus" работать с раскрывающимися списками прослушивателей событий, вы можете добавить их с событием onMouseDown вместо onClick. Таким образом, событие сработает, и после этого всплывающее окно закроется так:
<TogglePopupButton
onClick = { this.toggleDropup }
tabIndex = '0'
onBlur = { this.closeDropup }
/>
{ this.state.isOpenedDropup &&
<ul className = { dropupList }>
{ this.props.listItems.map((item, i) => (
<li
key = { i }
onMouseDown = { item.eventHandler }
>
{ item.itemName}
</li>
))}
</ul>
}
Добавьте обработчик onClick
в контейнер верхнего уровня и увеличивайте значение состояния всякий раз, когда пользователь щелкает внутри. Передайте это значение соответствующему компоненту, и всякий раз, когда значение изменяется, вы можете выполнять работу.
В этом случае мы вызываем this.closeDropdown()
всякий раз, когда clickCount
значение clickCount
.
Метод incrementClickCount
запускается в контейнере .app
но не в .dropdown
потому что мы используем event.stopPropagation()
для предотвращения появления событий.
Ваш код может выглядеть примерно так:
class App extends Component {
constructor(props) {
super(props);
this.state = {
clickCount: 0
};
}
incrementClickCount = () => {
this.setState({
clickCount: this.state.clickCount + 1
});
}
render() {
return (
<div className="app" onClick={this.incrementClickCount}>
<Dropdown clickCount={this.state.clickCount}/>
</div>
);
}
}
class Dropdown extends Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
componentDidUpdate(prevProps) {
if (this.props.clickCount !== prevProps.clickCount) {
this.closeDropdown();
}
}
toggleDropdown = event => {
event.stopPropagation();
return (this.state.open) ? this.closeDropdown() : this.openDropdown();
}
render() {
return (
<div className="dropdown" onClick={this.toggleDropdown}>
...
</div>
);
}
}
Я нашел это из статьи ниже:
render() {return ({this.node = node;}}> Переключить Popover {this.state.popupVisible && (Я поповер!)}); }}
Вот отличная статья об этой проблеме: "Обрабатывать клики за пределами компонентов React" https://larsgraubner.com/handle-outside-clicks-react/
Вы можете просто установить обработчик двойного щелчка на теле, а другой - на этот элемент. В обработчике этого элемента просто верните false, чтобы предотвратить распространение события. Поэтому, когда двойной щелчок происходит, если он находится на элементе, он будет пойман и не будет распространяться на обработчик на теле. В противном случае он будет пойман обработчиком на теле.
Обновление: если вы действительно не хотите предотвращать распространение событий, вам просто нужно использовать ближайший, чтобы проверить, произошел ли щелчок по вашему элементу или одному из его детей:
<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>