Виявлення клацання поза елементом (ванільний JavaScript)


84

Я шукав гарне рішення скрізь, але я не можу знайти такого, яке б не використовувало jQuery .

Чи існує крос-браузер, звичайний спосіб (без дивних хакерів або легкого злому коду), щоб виявити клацання поза елементом (який може мати чи не мати дітей)?


Чи встановило б обробник події onclick на тезі body?
Бенджамін Туг

Відповіді:


194

Додайте прослуховувач події 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, по якому клацнули. За допомогою цього ви перевіряєте себе і піднімаєте батьків до елемента документа, якщо один із них є вашим елементом спостереження.

Див. Приклад на 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, але одним з батьків є спостережуваний елемент (я просто підраховую рівень для цього). Ви також можете мати логічне значення var, якщо елемент знайдено чи ні, щоб не повернути обробник з речення 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в IE, щоб бути елементом, як це робить jQuery для своїх обробників подій. Існує безліч аргументів, щоб мати на увазі деякі шматочки jQuery;)


4
Що робити, якщо користувач натиснув дочірній елемент елемента?
Tiberiu-Ionuț Stan

3
Використовуючи це рішення, ви перевіряєте targetвластивість (яке, як каже метадінг, буде самим внутрішнім елементом DOM) - і всі його батьківські елементи. Або ви досягаєте елемента спостереження, що означає, що користувач клацнув всередині нього, або ви досягаєте елемента документа, і в цьому випадку він натиснув назовні. Як варіант, ви можете додати два прослуховувачі клацань - один на елемент документа, а інший на елемент спостереження, який використовує event.stopPropagation()/ cancelBubbleтак, щоб прослуховувач на елементі документа викликався лише клацанням поза переглянутого елемента.
JimmiTh

2
Це розумне використання for. З цікавості, чи є якісь причини не порівнювати елемент із змінної з цільовою подією (використовуючи суворе порівняння)?
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan так, якщо ви створюєте цей "годинник", використовуючи посилання замість ідентифікатора "x", я пропоную вам використовувати елемент === спостерігавЕлемент, використовуючи потрійні рівні.
метадінги

Ваше рішення чудово працює. Це може бути особистою проблемою, але я волів би поділитися нею. У мене така сама потреба, але натиснута кнопка показує 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('...')наприклад, у програмі responsejs / 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țStan у вас є доступ до всього списку вузлів, від батьківського до дітей, тому ви можете легко ними керувати.
hpapier

Раптом ваше рішення вже не просто.
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan Чому? Особисто я вважаю, що це рішення є найпростішим у середовищі React або складному середовищі JS. Не потрібні будь-які посилання, не потрібно реалізовувати рішення, якщо будинок встигає завантажитися, ви маєте більше контролю.
hpapier

0

Я провів багато досліджень щодо цього, щоб знайти кращий метод. Метод JavaScript. Містить рекурсивний перехід у 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.