检测点击外部元素(vanilla JavaScript)

时间:2013-01-07 01:21:21

标签: javascript

我到处搜索了一个很好的解决方案,但我找不到不使用jQuery 的解决方案。

是否存在跨浏览器,正常方式(没有奇怪的黑客或容易破解代码),检测元素外部的点击(可能有孩子也可能没有孩子)?

6 个答案:

答案 0 :(得分:108)

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

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) {
    alert('You clicked inside A')
  } else {
    alert('You clicked outside A')
  }
});
div {
  margin: auto;
  padding: 1em;
  max-width: 6em;
  background: rgba(0, 0, 0, .2);
  text-align: center;
}
Is the click inside A or outside?
<div id="a">A
  <div id="b">B
    <div id="c">C</div>
  </div>
</div>

答案 1 :(得分:25)

您需要在文档级别处理click事件。在事件对象中,您有一个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时,但其中一个父母是被监视的元素(我只是为此计算水平)。如果找到元素,您也可以使用boolean 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);
  }
};

这是跨浏览器兼容的代码,用于将事件监听器/处理程序(包括IE中的重写this作为元素附加,就像jQuery为其事件处理程序所做的那样。有很多论点要考虑一些jQuery;)

答案 2 :(得分:6)

这个怎么样:

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

答案 3 :(得分: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);

答案 4 :(得分:0)

解决此问题的另一种非常简单快捷的方法是将 def create_from_images(tfrecord_dir, image_dir, shuffle): print('Loading images from "%s"' % image_dir) image_filenames = sorted(glob.glob(os.path.join(image_dir, '*'))) if len(image_filenames) == 0: error('No input images found') img = np.asarray(PIL.Image.open(image_filenames[0])) resolution = img.shape[0] channels = img.shape[2] if img.ndim == 3 else 1 if img.shape[1] != resolution: error('Input images must have the same width and height') if resolution != 2 ** int(np.floor(np.log2(resolution))): error('Input image resolution must be a power-of-two') if channels not in [1, 3]: error('Input images must be stored as RGB or grayscale') with TFRecordExporter(tfrecord_dir, len(image_filenames)) as tfr: order = tfr.choose_shuffled_order() if shuffle else np.arange(len(image_filenames)) for idx in range(order.size): img = np.asarray(PIL.Image.open(image_filenames[order[idx]])) if channels == 1: img = img[np.newaxis, :, :] # HW => CHW else: img = img.transpose([2, 0, 1]) # HWC => CHW tfr.add_image(img) def add_image(self, img): if self.print_progress and self.cur_images % self.progress_interval == 0: print('%d / %d\r' % (self.cur_images, self.expected_images), end='', flush=True) if self.shape is None: self.shape = img.shape self.resolution_log2 = int(np.log2(self.shape[1])) assert self.shape[0] in [1, 3] assert self.shape[1] == self.shape[2] assert self.shape[1] == 2**self.resolution_log2 tfr_opt = tf.python_io.TFRecordOptions(tf.python_io.TFRecordCompressionType.NONE) for lod in range(self.resolution_log2 - 1): tfr_file = self.tfr_prefix + '-r%02d.tfrecords' % (self.resolution_log2 - lod) self.tfr_writers.append(tf.python_io.TFRecordWriter(tfr_file, tfr_opt)) assert img.shape == self.shape for lod, tfr_writer in enumerate(self.tfr_writers): if lod: img = img.astype(np.float32) img = (img[:, 0::2, 0::2] + img[:, 0::2, 1::2] + img[:, 1::2, 0::2] + img[:, 1::2, 1::2]) * 0.25 quant = np.rint(img).clip(0, 255).astype(np.uint8) ex = tf.train.Example(features=tf.train.Features(feature={ 'shape': tf.train.Feature(int64_list=tf.train.Int64List(value=quant.shape)), 'data': tf.train.Feature(bytes_list=tf.train.BytesList(value=[quant.tostring()]))})) tfr_writer.write(ex.SerializeToString()) self.cur_images += 1 的数组映射到侦听器返回的事件对象中。如果您元素的pathid名称与数组中的元素名称之一匹配,则单击位于元素内部。

(如果您不想直接获取元素(例如class,例如在ssr。中的reactjs / nextjs应用程序中,则此解决方案会很有用。)。

这里是一个例子:

document.getElementById('...')

我希望这个答案能对您有所帮助! (让我知道我的解决方案不是一个好的解决方案,或者您是否看到有待改进的地方。)

答案 5 :(得分:0)

为了找到更好的方法,我做了很多研究。 JavaScript方法.contains在DOM中递归检查其是否包含目标。我在react项目之一中使用了它,但是当react 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')
  }
})

WORKING DEMO jsfiddle