要素の外側のクリックを検出する(バニラJavaScript)


84

私はどこでも良い解決策を探しましたが、jQueryを使用ない解決策を見つけることができません。

要素の外側のクリックを検出するためのクロスブラウザー、通常の方法(奇妙なハッキングやコードの解読が簡単でない)はありますか(子がある場合とない場合があります)?


bodyタグにonclickイベントハンドラーを設定すると機能しますか?
ベンジャミントゥエグ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
私にとって最もエレガントな解決策のようです。
マリアン

3
これは私が今まで見た中で最高の解決策です。
HelloWorldの

25

クリックイベントはドキュメントレベルで処理する必要があります。イベントオブジェクトには、targetクリックされた最も内側のDOM要素であるプロパティがあります。これを使用して、自分自身をチェックし、その親の1つが監視対象の要素である場合は、ドキュメント要素までその親をたどります。

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でない場合、子がクリックされますが、その親の1つは監視された要素です(私は単にこれのレベルを数えています)。また、要素が見つかったかどうかに関係なく、for句内からハンドラーを返さないブール変数を使用することもできます。私の例は、何も一致しない場合にのみハンドラーが終了するように制限しています。

クロスブラウザの互換性を追加して、私は通常次のようにしています:

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);
  }
};

これはthis、jQueryがイベントハンドラーに対して行うように、IEでの包括的書き換えを含むイベントリスナー/ハンドラーを要素としてアタッチするためのブラウザー間互換コードです。jQueryのいくつかのビットを念頭に置くための議論はたくさんあります;)


4
ユーザーが要素の子をクリックした場合はどうなりますか?
Tiberiu-Ionuț Stan 2013

3
このソリューションを使用して、targetプロパティ(メタディングが言うように、最も内側のDOM要素になります)とそのすべての親要素を確認します。監視対象要素に到達するか、つまりユーザーがその内部をクリックしたか、ドキュメント要素に到達します。この場合、ユーザーは外部をクリックします。または、2つのクリックリスナーを追加することもできます。1つはドキュメント要素に、もう1つは監視要素に追加します。これはevent.stopPropagation()/を使用cancelBubbleして、ドキュメント要素のリスナーが監視要素の外側のクリックによってのみトリガーされるようにします。
JimmiTh 2013年

2
これは、の巧妙な使用法ですfor。好奇心から、変数の要素をイベントのターゲットと比較しない理由はありますか(厳密な比較を使用)?
Tiberiu-Ionuț Stan 2013

@ Tiberiu-IonuțStanはい、ID「x」の代わりに参照を使用してこの「ウォッチ」を作成する場合は、トリプルイコールを使用して要素=== watchedElementを使用することをお勧めします。
メタディング2013年

あなたのソリューションはうまく機能します。これは個人的な問題かもしれませんが、私はむしろそれを共有したいと思います。同じニーズがありますが、ボタンをクリックすると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

この問題に対するもう1つの非常に単純で迅速なアプローチは、の配列をpathリスナーから返されたイベントオブジェクトにマップすることです。要素のidorclass名が配列内の名前のいずれかと一致する場合、クリックは要素内にあります。

(このソリューションは、要素を直接取得したくない場合に役立ちます(たとえばdocument.getElementById('...')、reactjs / nextjsアプリ、ssr ..など)。

次に例を示します。

   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țStan19年

@ Tiberiu-IonuțStanは、親から子まで、ノードリスト全体にアクセスできるため、簡単に操作できます。
hpapier

突然、あなたの解決策はもはや単純ではなくなりました。
Tiberiu-IonuțStan19年

@ Tiberiu-IonuțStanなぜですか?個人的には、このソリューションはReactまたは複雑なJS環境で最も簡単だと思います。参照は必要ありません。DOMの読み込みに時間がかかる場合は、ソリューションを実装する必要はありません。より詳細に制御できます。
hpapier

0

より良い方法を見つけるために、私はそれについて多くの研究をしました。JavaScriptメソッド.containsは、DOMで再帰的に移動して、ターゲットが含まれているかどうかを確認します。反応プロジェクトの1つで使用しましたが、セット状態で反応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.