Phát hiện phần tử bên ngoài nhấp chuột (JavaScript vani)


84

Tôi đã tìm kiếm một giải pháp tốt ở khắp mọi nơi, nhưng tôi không thể tìm thấy giải pháp nào không sử dụng jQuery .

Có cách nào thông thường trên nhiều trình duyệt (không có các hack kỳ lạ hoặc mã dễ phá vỡ), để phát hiện nhấp chuột bên ngoài một phần tử (có thể có hoặc không có phần tử con) không?


Việc đặt trình xử lý sự kiện onclick trên thẻ body có thực hiện được công việc không?
Benjamin Toueg

Câu trả lời:


192

Thêm trình xử lý sự kiện vào documentvà sử dụng Node.contains()để tìm xem mục tiêu của sự kiện (là phần tử được nhấp chuột nhiều nhất) có bên trong phần tử được chỉ định của bạn hay không. Nó hoạt động ngay cả trong 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
Có vẻ là giải pháp thanh lịch nhất đối với tôi.
Marian

3
Đó là giải pháp tốt nhất mà tôi từng thấy.
HelloWorld

25

Bạn cần xử lý sự kiện nhấp chuột ở cấp độ tài liệu. Trong đối tượng sự kiện, bạn có một thuộc targettính, phần tử DOM bên trong nhất đã được nhấp. Với điều này, bạn tự kiểm tra và đi lên phần tử cha mẹ của nó cho đến phần tử tài liệu, nếu một trong số chúng là phần tử bạn đã xem.

Xem ví dụ trên 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";
});

Như mọi khi, tính năng này không tương thích với nhiều trình duyệt xấu vì addEventListener / attachmentEvent, nhưng nó hoạt động như thế này.

Một đứa trẻ được nhấp vào, khi không phải event.target, nhưng một trong số đó là cha mẹ của nó là phần tử đã theo dõi (tôi chỉ đơn giản là đang đếm cấp độ cho điều này). Bạn cũng có thể có một boolean var, nếu phần tử được tìm thấy hoặc không, để không trả về trình xử lý từ bên trong mệnh đề for. Ví dụ của tôi là giới hạn ở chỗ người xử lý chỉ kết thúc, khi không có gì phù hợp.

Thêm khả năng tương thích giữa các trình duyệt, tôi thường làm như thế này:

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

Đây là mã tương thích với nhiều trình duyệt để đính kèm trình xử lý / xử lý sự kiện, viết lại bao gồm thistrong IE, làm phần tử, giống như jQuery làm cho trình xử lý sự kiện của nó. Có rất nhiều đối số để ghi nhớ một số bit jQuery;)


4
Điều gì sẽ xảy ra nếu người dùng nhấp vào một phần tử con?
Tiberiu-Ionuț Stan

3
Bằng cách sử dụng giải pháp này, bạn kiểm tra thuộc targettính (như siêu mục nói, sẽ là phần tử DOM bên trong nhất) - và tất cả các phần tử mẹ của nó. Bạn tiếp cận phần tử đã xem của mình, có nghĩa là người dùng đã nhấp vào bên trong nó hoặc bạn tiếp cận phần tử tài liệu, trong trường hợp đó họ nhấp vào bên ngoài. Ngoài ra, bạn có thể thêm hai trình nghe lần nhấp - một trên phần tử tài liệu và một trên phần tử đã xem, sử dụng event.stopPropagation()/ cancelBubbleđể trình nghe trên phần tử tài liệu chỉ được kích hoạt bằng một lần nhấp bên ngoài phần tử đã xem.
JimmiTh

2
Đó là một số cách sử dụng thông minh for. Vì tò mò, có lý do gì để không so sánh một phần tử từ một biến với mục tiêu trên sự kiện (sử dụng so sánh chặt chẽ)?
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțĐược, nếu bạn tạo 'đồng hồ' này bằng cách sử dụng tham chiếu thay vì id 'x', tôi khuyên bạn nên sử dụng phần tử === watchingElement bằng cách sử dụng ba dấu bằng.
metadings

Giải pháp của bạn hoạt động tuyệt vời. Đây có thể là một vấn đề cá nhân, nhưng tôi muốn chia sẻ nó. Tôi cũng có nhu cầu tương tự, nhưng một nút được nhấp vào sẽ hiển thị một div mà khi người dùng nhấp vào bên ngoài, sẽ bị ẩn đi. Vấn đề với giải pháp này là div không bao giờ được hiển thị nữa. Tôi đã chia mã của bạn ở đây để làm ví dụ: jsfiddle.net/bx5hnL2h
Alexandre Schmidt

7

Còn cái này thì sao:

jsBin demo

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

Để ẩn phần tử bằng cách nhấp vào bên ngoài nó, tôi thường áp dụng mã đơn giản như vậy:

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

Một cách tiếp cận rất đơn giản và nhanh chóng khác cho vấn đề này là ánh xạ mảng pathvào đối tượng sự kiện được trả về bởi trình nghe. Nếu tên idhoặc classphần tử của bạn khớp với một trong những tên đó trong mảng, thì nhấp chuột nằm bên trong phần tử của bạn.

(Giải pháp này có thể hữu ích nếu bạn không muốn lấy phần tử trực tiếp (ví document.getElementById('...')dụ:, ví dụ: trong ứng dụng reactjs / nextjs, trong ssr ..).

Đây là một ví dụ:

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

Tôi hy vọng câu trả lời này sẽ giúp bạn! (Hãy cho tôi biết nếu giải pháp của tôi không phải là giải pháp tốt hoặc nếu bạn thấy điều gì đó cần cải thiện.)


Điều gì về các phần tử lồng nhau?
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan bạn có quyền truy cập vào toàn bộ danh sách nút, từ cha mẹ đến con, vì vậy bạn có thể thao tác chúng một cách dễ dàng.
hpapier

Suddly giải pháp của bạn không còn đơn giản nữa.
Tiberiu-Ionuț Stan

@ Tiberiu-IonuțStan Tại sao? Cá nhân tôi thấy giải pháp này là đơn giản nhất trong môi trường React hoặc JS phức tạp. Không cần bất kỳ giới thiệu nào, không cần thực hiện một giải pháp nếu dom tạo thời gian để tải, bạn có nhiều quyền kiểm soát hơn.
hpapier

0

Tôi đã nghiên cứu rất nhiều về nó để tìm ra một phương pháp tốt hơn. Phương thức JavaScript .contains đi đệ quy trong DOM để kiểm tra xem nó có chứa đích hay không. Tôi đã sử dụng nó trong một dự án phản ứng nhưng khi DOM phản ứng thay đổi trên trạng thái đã đặt, phương thức .contains không hoạt động. VẬY tôi đã nghĩ ra giải pháp này

//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')
  }
})

LÀM VIỆC DEMO jsfiddle

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.