单击标签元素内的文本时,Javascript事件侦听器将触发两次

时间:2018-10-18 08:04:39

标签: javascript label addeventlistener

如何在单击标签内的文本时使函数不触发两次。

如果我使用event.preventDefault(),则用于使复选框处于选中状态的基本浏览器功能也将停止工作。

const label = document.querySelector('.parent');

label.addEventListener('click', handleLabelClick);

function handleLabelClick(event) {
	console.log('Clicked')
}
<div class="parent">
  <label for="option1">
    <span>Select me</span>
    <input id="option1" type="checkbox">
  </label>
</div>

2 个答案:

答案 0 :(得分:2)

据我了解,您希望点击.parent元素以触发点击处理程序,但您不希望针对与.parent中的复选框或其标签相关的点击触发该处理程序。 / p>

两种方法:

  1. 为调用label的{​​{1}}添加处理程序,或

  2. 在事件处理程序中检查事件是否通过stopPropagation

方法1:

label
const parent = document.querySelector('.parent');

parent.addEventListener('click', handleLabelClick);

// Stop clicks in the label or checkbox from propagating to parent
parent.querySelector("label").addEventListener("click", function(event) {
  event.stopPropagation();
});

function handleLabelClick(event) {
	console.log('Clicked');
}
.parent {
  border: 1px solid #ddd;
}

这里的方法2:

<div class="parent">
  <label for="option1">
    <span>Select me</span>
    <input id="option1" type="checkbox">
  </label>
</div>
const parent = document.querySelector('.parent');

parent.addEventListener('click', handleLabelClick);

function handleLabelClick(event) {
  const label = event.target.closest("label");
  if (label && this.contains(label)) {
    // Ignore this click
    return;
  }
	console.log('Clicked');
}
.parent {
  border: 1px solid #ddd;
}

答案 1 :(得分:0)

这是标准的(不幸的)浏览器行为。
属性for<label>分配给<input>,因此,当单击<label>元素时,浏览器将在您真正点击后立即模拟对<input>元素的点击。
这允许文本<input>的焦点,单选<input>的切换或复选框<input>的切换。
但是对于不幸的部分,这也会导致两个元素都发送 click 事件。如果您正在监听父元素的点击,这意味着您将两次收到一次“人机交互”。一次来自<input>,一次来自“被点击的元素”。

对于那些想知道<input> could be inside <label>元素的人来说,样式的可能性会降低,但是您可以在复选框和文本之间单击。

您将<span><input>放在<label>中实际上创建了一个不错的测试用例。
尝试从左到右点击;
在文本上,您将收到SPAN + INPUT事件,
在文本和复选框之间,您将获得LABEL + INPUT事件,
在复选框上仅直接输入事件,
然后再继续,只有DIV事件。 (因为DIV是一个块元素,并且一直向右延伸)

一种解决方案是仅监听<input>个元素事件,但是您将不会捕获<div>次点击,也无法将<div>放在<label>内。

最简单的方法是忽略<label>上的点击以及<label>中除<input>之外的所有可点击元素。在这种情况下,<span>

const elWrapper = document.querySelector('.wrapper');

elWrapper.addEventListener('click', handleLabelClick);

function handleLabelClick(event) {
	console.log('Event received from tagName: '+event.target.tagName);
	if (event.target.tagName === 'LABEL' || event.target.tagName === 'SPAN') { return; }
	console.log('Performing some action only once. Triggered by click on: '+event.target.tagName);
}
<div class="wrapper">
  <label for="option1">
    <span>Select me</span>
    <input id="option1" type="checkbox">
  </label>
</div>

*)在我的示例中,我将类名称 parent 更改为 wrapper ,并将包含div元素的变量名称 label 更改为 elWrapper , 因为父级标签是关键字,将它们用于其他方式可能会造成混淆。

@ T.J。的解决方案导致<label>中的事件及其中的所有事件在以后被忽略。 要触发事件,您需要点击<div>上的某个位置,而不是直接在文本或复选框上单击。

我添加了我的答案,因为我不认为这是OP的意图。

但是,即使是这种情况,您也可以使用上面提供的类似方法。检查单击的元素名称是否为DIV,然后允许采取进一步措施。我认为它更直接,更本地化(不影响事件传播),更通用(如果您选择捕获:addEventListener(...,...,true)仍然可以使用)