为什么对于label元素,点击事件会触发两次onclick?

51

      window.onload = function(){
         var wow = document.getElementById("wow");
      wow.onclick = function(){
          alert("hi");
      }
      }
    <label id="wow"><input type="checkbox" name="checkbox" value="value">Text</label>

这是我的代码,当我点击“文字”时,它会弹出两次“hi”,但当我点击方框时,onclick元素只会触发一次,为什么?


你需要在 Windows 的 onload 事件上弹出警告吗? - bumbumpaw
@Niang 当文本被点击时不行。 - dramasea
8个回答

85
当你点击标签时,它会触发点击处理程序并弹出一个警告。但是,点击标签也会自动向关联的输入元素发送一个点击事件,因此这被视为对复选框的点击。然后事件冒泡导致该点击事件在包含元素(即标签)上触发,因此您的处理程序再次运行。如果你将你的HTML改成这样,你就不会得到双倍的警告:
<input id="wowcb" type="checkbox" name="checkbox" value="value">
<label id="wow" for="wowcb">Text</label>

演示


1
我想我从来没有注意到这一点,因为通常我会将“click”处理程序放在<input>上,而不是标签上。这是一种奇怪的行为,我不确定是否能想到它有用的原因。 - Pointy
你也可以停止事件传播。 - M Miller
6
最好绑定一个“change”事件处理程序,这样更有利于可访问性。 - Felix Kling
@FelixKling 据我所知,您无法将更改处理程序绑定到标签。 - Barmar
1
@MMiller 是的,但你需要在复选框上添加一个点击处理程序。 - Barmar
显示剩余16条评论

5

如果您的意图是仅响应标签上的点击而不是复选框上的点击,则可以查看event.target属性。它引用调用侦听器的元素,因此如果单击不在该元素上,则不执行操作:

window.onload = function(){
  var el = document.getElementById('wow');
  el.addEventListener('click', function(event) {

    if (this === event.target) {
      /* click was on label */
      alert('click was on label');

    } else {
      /* click was on checkbox */
      event.stopPropagation();
      return false;
    }

  }, false);
}

另一方面,如果你只想响应复选框的点击(其中标签的点击也会触发复选框的点击),那么请反过来做。对于标签的点击不做任何操作,让复选框的点击通过:

window.onload = function(){
  var el = document.getElementById('foolabel');
  el.addEventListener('click', function(event) {

    if (this === event.target) {
      /* click was on label */
      event.stopPropagation();
      return false;

    } else {

      /*
      ** click is from checkbox, initiated by click on label
      ** or checkbox                                  
      */

      alert('click from checkbox');
    }

  }, false);
}

这个版本似乎具有最自然的行为。但是,如果更改标记使标签不再包装复选框,则意味着侦听器将不会被调用。


4

事件冒泡。

复选框是标签的子节点。当你点击复选框时,事件会冒泡到标签上。然后弹出两次警报。

为了防止在点击复选框时弹出两次警报,你可以将onclick函数更改为以下内容:

wow.onclick = function(e){
    alert('hi');
    stopBubble(e);
}


function stopBubble(e)  
{  
    if (e && e.stopPropagation)  
        e.stopPropagation()  
    else 
        window.event.cancelBubble=true 
}  

希望这对你有用。


1
我认为这不是正确的答案。包含<label>标签的事件处理程序应仅接收一次冒泡事件。浏览器会生成两个“click”事件,一个以<input>元素为目标,另一个以<label>为目标。 - Pointy
弹出两次。一个来自于点击标签,另一个是从输入气泡到标签的点击。我想这可以解释清楚:) - Tyler.z.yang
不,冒泡仅涉及到一个事件对象,在父元素上的事件处理程序中传递。在这种情况下,处理程序被调用两次时,事件对象是不同的。 - Pointy
它不起作用:http://jsfiddle.net/barmar/fMMAz/5/ 从标签到复选框的传播不是冒泡,而是与标签非常特定的东西。 - Barmar
不是这样的。我在Chrome中点击了“文本”,得到了两个警报。 - Barmar
@Pointy 对不起,伙计们,我搞混了 :( - Tyler.z.yang

1

看这个:

document.getElementById("winput").addEventListener('click', function(event){
     alert('input click');

     //stop bubble
     event.stopPropagation();

 }, false); 

http://jsfiddle.net/96vPP/


0
使用change来监听事件,而不是click

0
这可能是最简单的答案。只需在文本周围添加一个 span 并停止事件传播即可。

window.onload = function(){
    var wow = document.getElementById("wow");
    wow.onclick = function(){
        alert("hi");
    }
}
<label id="wow">
    <input type="checkbox" name="checkbox" value="value">
    <span onclick="event.stopPropagation()">Text</span>
</label>

或者不带内联JavaScript

window.onload = function(){
    var wow = document.getElementById("wow");
    wow.onclick = function(){
        alert("hi");
    }

    var span = document.getElementsByTagName("span")[0];
    span.onclick = function(event){
        event.stopPropagation();
    }
}
<label id="wow">
    <input type="checkbox" name="checkbox" value="value">
    <span>Text</span>
</label>


这两种解决方案都不能百分之百地正常工作。你有多个空格字符落在<span>中,它们分别位于:复选框前,在“文本”前,就在复选框和“文本”之间,如果你点击得恰到好处,就会出现双重警报。此外,如果你非常准确地点击复选框边框外部的1像素处,也会出现双重警报。(我使用的是Windows 10,Chrome 98) - Zectbumo

0

又一次撞上了这个陷阱,决定亲自验证它的作用,希望能帮助自己记住。为了帮助那些可能不太清楚上面内容的人,我做了一个例子。

https://jsfiddle.net/ashes/0bcauenm/

$(".someBigContainer").on("click", "input[type=checkbox]", ()=> {
    $output.val("Clicked: checkbox\n" + $output.val());
}); 

编辑:添加了代码片段的链接。


0
对于那些使用React和Material UI的人 - 我在我的一个表单中遇到了这个问题。 event.preventDefault() 阻止了事件像被接受的答案所描述的那样冒泡。
  <Button
    onClick={(event) => {
      event.preventDefault()
      this.handleCardClick(planName)
    }}
  >
    <FormControl component="fieldset">
      <RadioGroup
        row
        value={selectedPlan}
      >
        <FormControlLabel
          control={<Radio color="secondary" />}
          label={label}
        />
      </RadioGroup>
    </FormControl>
  </Button>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接