요소 외부 클릭 감지 (바닐라 자바 ​​스크립트)


84

나는 모든 곳에서 좋은 솔루션을 찾았지만 jQuery를 사용 하지 않는 솔루션을 찾을 수 없습니다 .

크로스 브라우저, 정상적인 방법 (이상한 해킹이나 코드를 깨기 쉬운 코드없이)이 있는가? (하위가있을 수도 있고 없을 수도있는) 요소 외부의 클릭을 감지 할 수 있습니까?


본문 태그에 onclick 이벤트 핸들러를 설정하면 작업이 수행됩니까?
Benjamin Toueg 2013 년

답변:


192

에 이벤트 리스너를 추가 document하고를 사용 Node.contains()하여 이벤트 대상 (가장 안쪽에서 클릭 한 요소)이 지정된 요소 내부에 있는지 확인합니다. IE5에서도 작동합니다.

var specifiedElement = document.getElementById('a');

//I'm using "click" but it works with any event
document.addEventListener('click', function(event) {
  var isClickInside = specifiedElement.contains(event.target);

  if (!isClickInside) {
    //the click was outside the specifiedElement, do something
  }
});


14
나에게 가장 우아한 해결책 인 것 같습니다.
Marian

3
내가 본 최고의 솔루션입니다.
HelloWorld

25

문서 수준에서 클릭 이벤트를 처리해야합니다. 이벤트 개체에는 target클릭 한 가장 안쪽에있는 DOM 요소 인 속성이 있습니다. 이를 통해 자신을 확인하고 문서 요소 중 하나가 감시 요소 인 경우 해당 부모를 문서 요소까지 걸어갑니다.

jsFiddle 의 예를 참조하십시오.

document.addEventListener("click", function (e) {
  var level = 0;
  for (var element = e.target; element; element = element.parentNode) {
    if (element.id === 'x') {
      document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked";
      return;
    }
    level++;
  }
  document.getElementById("out").innerHTML = "not x clicked";
});

항상 그렇듯이 addEventListener / attachEvent로 인해 브라우저 간 호환이되지는 않지만 이렇게 작동합니다.

event.target이 아닐 때 자식이 클릭되지만 부모 중 하나는 감시 된 요소입니다 (단순히 레벨을 계산합니다). 요소가 있는지 여부에 관계없이 for 절 내부에서 처리기를 반환하지 않도록 부울 var를 사용할 수도 있습니다. 내 예는 아무것도 일치하지 않을 때 처리기가 완료되는 것으로 제한합니다.

브라우저 간 호환성을 추가하면 일반적으로 다음과 같이합니다.

var addEvent = function (element, eventName, fn, useCapture) {
  if (element.addEventListener) {
    element.addEventListener(eventName, fn, useCapture);
  }
  else if (element.attachEvent) {
    element.attachEvent(eventName, function (e) {
      fn.apply(element, arguments);
    }, useCapture);
  }
};

이것은 이벤트 리스너 / 핸들러를 연결하기위한 브라우저 간 호환 코드이며, thisIE에서 재 작성 을 포함 하여 jQuery가 이벤트 핸들러에 대해 수행하는 것처럼 요소가됩니다. jQuery를 염두에 두어야 할 인수가 많이 있습니다.)


4
사용자가 요소의 하위 항목을 클릭하면 어떻게됩니까?
Tiberiu-누트 스탠

3
이 솔루션을 사용하여 target속성 (메타 딩에서 말했듯이 가장 안쪽의 DOM 요소)과 모든 부모 요소를 확인합니다. 사용자가 내부를 클릭했음을 의미하는 감시 된 요소에 도달하거나 외부를 클릭 한 문서 요소에 도달합니다. 또는 두 개의 클릭 리스너를 추가 할 수 있습니다. 하나는 문서 요소에, 다른 하나는 감시 된 요소에 추가 할 수 있습니다.이 경우 event.stopPropagation()/ 를 사용 cancelBubble하여 문서 요소의 리스너는 감시 된 요소 외부의 클릭에 의해서만 트리거됩니다.
JimmiTh

2
그것은 for. 호기심에서 변수의 요소를 이벤트의 대상과 비교하지 않을 이유가 있습니까 (엄격한 비교 사용)?
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan 예, id 'x'대신 참조를 사용하여이 'watch'를 생성하는 경우 triple equals를 사용하여 element === watchedElement를 사용하는 것이 좋습니다.
metadings

귀하의 솔루션은 훌륭하게 작동합니다. 이것은 개인적인 문제 일 수 있지만 공유하고 싶습니다. 나는 같은 필요가 있지만 클릭 한 버튼은 사용자가 외부를 클릭 할 때 숨겨져 야하는 div를 보여줍니다. 이 솔루션의 문제점은 div가 다시 표시되지 않는다는 것입니다. 예를 들어 여기에 코드를 포크했습니다. jsfiddle.net/bx5hnL2h
Alexandre Schmidt

7

이건 어때:

jsBin 데모

document.onclick = function(event){
  var hasParent = false;
    for(var node = event.target; node != document.body; node = node.parentNode)
    {
      if(node.id == 'div1'){
        hasParent = true;
        break;
      }
    }
  if(hasParent)
    alert('inside');
  else
    alert('outside');
} 

0

외부를 클릭하여 요소를 숨기려면 보통 다음과 같은 간단한 코드를 적용합니다.

var bodyTag = document.getElementsByTagName('body');
var element = document.getElementById('element'); 
function clickedOrNot(e) {
	if (e.target !== element) {
		// action in the case of click outside 
		bodyTag[0].removeEventListener('click', clickedOrNot, true);
	}	
}
bodyTag[0].addEventListener('click', clickedOrNot, true);


0

이 문제에 대한 또 다른 매우 간단하고 빠른 접근 방식 path은 리스너가 반환 한 이벤트 객체에 배열을 매핑하는 것 입니다. 요소 의 id또는 class이름이 배열의 이름 중 하나와 일치하면 클릭이 요소 내부에있는 것입니다.

(이 솔루션은 요소를 직접 가져 오지 않으려는 경우 유용 할 수 있습니다 (예 : document.getElementById('...')ssr ..의 reactjs / nextjs 앱).

다음은 그 예입니다.

   document.addEventListener('click', e => {
      let clickedOutside = true;

      e.path.forEach(item => {
        if (!clickedOutside)
          return;

        if (item.className === 'your-element-class')
          clickedOutside = false;
      });

      if (clickedOutside)
        // Make an action if it's clicked outside..
    });

이 답변이 도움이 되었기를 바랍니다. (내 솔루션이 좋은 솔루션이 아니거나 개선 할 점이 있으면 알려주세요.)


중첩 된 요소는 어떻습니까?
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan은 부모에서 자식까지 전체 노드 목록에 액세스 할 수 있으므로 쉽게 조작 할 수 있습니다.
hpapier 1911

갑자기 솔루션이 더 이상 단순하지 않습니다.
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan 왜? 개인적으로이 솔루션은 React 또는 복잡한 JS 환경에서 가장 간단합니다. 어떤 심판도 필요하지 않으며, dom이로드 할 시간을 만들면 솔루션을 구현할 필요가 없으며 더 많은 제어 권한이 있습니다.
hpapier 19

0

더 나은 방법을 찾기 위해 많은 연구를했습니다. JavaScript 메소드 .contains는 DOM에서 재귀 적으로 이동하여 대상이 포함되어 있는지 여부를 확인합니다. 반응 프로젝트 중 하나에서 사용했지만 설정된 상태에서 DOM이 변경되면 .contains 메서드가 작동하지 않습니다. 그래서 나는이 해결책을 생각해 냈습니다.

//Basic Html snippet
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
<div id="mydiv">
  <h2>
    click outside this div to test
  </h2>
  Check click outside 
</div>
</body>
</html>


//Implementation in Vanilla javaScript
const node = document.getElementById('mydiv')
//minor css to make div more obvious
node.style.width = '300px'
node.style.height = '100px'
node.style.background = 'red'

let isCursorInside = false

//Attach mouseover event listener and update in variable
node.addEventListener('mouseover', function() {
  isCursorInside = true
  console.log('cursor inside')
})

/Attach mouseout event listener and update in variable
node.addEventListener('mouseout', function() {
    isCursorInside = false
  console.log('cursor outside')
})


document.addEventListener('click', function() {
  //And if isCursorInside = false it means cursor is outside 
    if(!isCursorInside) {
    alert('Outside div click detected')
  }
})

작업 데모 jsfiddle

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.