在Firefox中,如果鼠标在点击时移动,HTML标签无法触发相应的输入。

13

在下面的示例中,当您单击标签时,输入框的状态会改变。

document.querySelector("label").addEventListener("click", function() {
  console.log("clicked label");
});
label {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
<input type="checkbox" id="1">
<label for="1">Label</label>

在Chrome中,当您在mousedownmouseup事件之间移动光标时,输入仍会被触发,而在Firefox中,复选框不会改变状态。

有没有办法解决这个问题?(不使用JavaScript事件监听器)

Firefox版本:69.0.3(64位)

在Chrome中执行的完整操作步骤:

  1. 在标签上按下按钮
  2. 在保持按下按钮的情况下移动光标(即使在标签外部)
  3. 将光标返回到标签
  4. 释放按钮

@TemaniAfif,你现在在Chrome上也是这种情况吗?(因为我已经澄清了我正在做什么)。还是它仍然没有改变复选框的状态? - nick zoum
1
如果我们将鼠标悬停在标签上,那么就没问题。移动不应该影响这一点,所以我想我们面临的是Firefox的一个bug。 - Temani Afif
2
这是一个错误,已在Firefox错误门户网站上记录为“未确认”状态,“只有当mousedown和mouseup发生在同一位置时,才应该发生“点击”事件”。您可以在以下错误网址中检查:https://bugzilla.mozilla.org/show_bug.cgi?id=319347 - Jadli
@AmitJadli 感谢您的挖掘。我的答案本应包含此内容,但是我在手机上找不到。 - Benjamin James Kippax
1
@TemaniAfif 我同意,即使将光标移动1px也会破坏交互。 - nick zoum
显示剩余2条评论
5个回答

3

介绍

虽然我在问题中明确说明答案不应涉及JavaScript,但所有答案都使用了JavaScript。
由于这似乎是Firefox的bug,并且目前提交的大多数答案需要我改变我的其他代码,因此我决定创建一个可运行一次的脚本,将处理所有标签,无论它们何时添加到dom中,并对我的其他脚本影响最小。

解决方案 - 示例

var mutationConfiguration = {
  attributes: true,
  childList: true
};

if (document.readyState === "complete") onLoad();
else addEventListener("load", onLoad);

var managingDoms = [];

function onLoad() {
  document.querySelectorAll("label[for]").forEach(manageLabel);
  if (typeof MutationObserver === "function") {
    var observer = new MutationObserver(function(list) {
      list.forEach(function(item) {
        ({
          "attributes": function() {
            if (!(item.target instanceof HTMLLabelElement)) return;
            if (item.attributeName === "for") manageLabel(item.target);
          },
          "childList": function() {
            item.addedNodes.forEach(function(newNode) {
              if (!(newNode instanceof HTMLLabelElement)) return;
              if (newNode.hasAttribute("for")) manageLabel(newNode);
            });
          }
        }[item.type])();
      });
    });
    observer.observe(document.body, mutationConfiguration);
  }
}

function manageLabel(label) {
  if (managingDoms.includes(label)) return;
  label.addEventListener("click", onLabelClick);
  managingDoms.push(label);
}

function onLabelClick(event) {
  if (event.defaultPrevented) return;
  var id = this.getAttribute("for");
  var target = document.getElementById(id);
  if (target !== null) {
    this.removeAttribute("for");
    var self = this;
    target.click();
    target.focus();
    setTimeout(function() {
      self.setAttribute("for", id);
    }, 0);
  }
}
label {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  padding: 10px;
  border: 1px solid black;
  cursor: pointer;
}
<input type="checkbox" id="a">
<input type="text" id="b">
<label for="a">A</label>
<script>
  setTimeout(function() {
    var label = document.createElement("label");
    label.setAttribute("for", "b");
    label.textContent = "b";
    document.body.appendChild(label);
  }, 3E3);
</script>

说明

onLabelClick

每当标签被点击时,需要调用函数onLabelClick。它将检查该标签是否有对应的输入元素。如果有,它将触发它,删除标签的for属性,以使浏览器不会再次触发它并使用 setTimeout 延迟0毫秒后将for属性添加回来。这意味着无需调用event.preventDefault,因此不会取消其他操作或事件。如果需要覆盖此功能,只需添加调用Event#preventDefault或删除for属性的事件监听器。

manageLabel

函数manageLabel接受标签并检查是否已经添加了事件侦听器以避免重新添加,如果尚未添加,则添加侦听器,并将其添加到已管理过的标签列表中。

onLoad

需要在页面加载时调用函数onLoad,以便可以对那一刻DOM上所有的标签进行调用函数manageLabel。该函数还使用MutationObserver来捕获在加载完成(脚本已运行)之后添加的任何标签。

以上代码由Martin Barker优化。


你的代码有点不够优化,做了一些不必要的检查和一些昂贵的CPU周期。我已经为你进行了优化,可以在https://pastebin.com/FDXwQL1d上找到。 - Barkermn01
它不会删除检查,而是用 HTMLLabelElement 检查替换了一个 HTMLElement 和一个标记名称为标签的检查。 - Barkermn01
1
聪明但是过于复杂。 - Steve Tomlin

2

我知道你不想要JS事件监听器,但我认为你的意思是识别移动,而不是使用mousedown而不是click(mousedown后跟mouseup)。

虽然这是Firefox中已知的一个错误,但您可以通过使用mousedown事件来解决它。

我不得不更改您的ID以使其有效,ID必须以字符开头。

document.querySelector("label").addEventListener("mousedown", function(evt) {
  console.log("clicked label");
  // if you want to to check the checkbox when it happens,
  let elmId = evt.target.getAttribute("for")
  let oldState = document.querySelector("#"+elmId).checked;
  setTimeout(() => {
    if(oldState == document.querySelector("#"+elmId).checked){
      document.querySelector("#"+elmId).checked = !oldState;
    }
  }, 150)
});
label {
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}
<input type="checkbox" id="valid_1">
<label for="valid_1">Label</label>


问题在于我必须在页面加载时运行此脚本(使用 querySelectorAll),然后每次将标签添加到 dom 中都要运行。这也可能会破坏添加到标签或 dom 中的任何鼠标事件侦听器,因为您使用 Event#preventDefault 来避免在不具有此错误的浏览器上双击。最后,使用 click 事件会更好,因为它会具有与预期操作相同的交互。除此之外,这是迄今为止最好的答案。 - nick zoum
@nickzoum 我已经更新了代码,以解决你的 preventDefault() 问题。现在它会检查浏览器是否已更改,如果没有更改,则进行更改。这个过程只需要150毫秒,足够快,让用户不会注意到它,同时又足够慢,让浏览器及时执行它应该执行的操作。这样好吗? - Barkermn01

1
不,这似乎是Firefox的一个bug,与你的代码无关。我认为没有css的解决方法可以解决这个问题。您可以向Mozilla报告此问题并获得修复,但我不会依赖它。 https://bugzilla.mozilla.org/home 对于潜在的解决方法,我建议改为在mouseup上触发事件。

0

没有使用JavaScript时,当您单击标签并且其“for”值与输入的“id”值相同时,则会单击该输入,但是这在浏览器中不一致。

如果浏览器遵循上述规则,则您的JavaScript单击事件将取消该效果,最终什么也不做。

解决方案

为了在各种浏览器中保持一致性,您可以采用不同的策略:在加载时动态更改所有属性“for”为“data-for”,因此它会使原始浏览器效果无效。然后,您可以将单击事件应用于每个标签。

var replaceLabelFor = function () {
    var $labels = document.querySelectorAll('label');
    var arrLabels = Array.prototype.slice.call($labels);
    arrLabels.forEach(function (item) {
      var att = document.createAttribute('data-for');
      att.value = String(this.for);
      item.setAttributeNode(att);
      item.removeAttribute('for')
    });
}

var applyMyLabelClick() {
  document.querySelector("label").addEventListener("click", function() {
    console.log("clicked label");
  });
}

// x-browser handle onload
document.attachEvent("onreadystatechange", function(){
  if(document.readyState === "complete"){
    document.detachEvent("onreadystatechange", arguments.callee);
    replaceLabelFor();
    applyMyLabelClick();
  }
});


“document.attachEvent(“onreadystatechange”,)”我只是好奇为什么你选择它而不是“document.addEventListener(“DOMContentLoaded”,)”? - Barkermn01
这只是我看到的一个方法,它更加适用于跨浏览器。选择你喜欢的任何一种。 - Steve Tomlin

-2

将事件附加到文档并定位所需的元素应该可以解决此问题。

$(document).on('click', '.item', function(event) {});

从过去的阅读中得知,这是因为 Firefox 将您的操作视为拖动元素的尝试。然而,由于用户选择为 none,它只是阻止了默认行为。

这基于相当有限的知识,但似乎是一个已知的错误/怪癖,并且有一些文章支持此观点。


我在问题中明确表示“不使用JavaScript事件监听器”。 - nick zoum
@nickzoum,由于这是Firefox中已知的错误,我认为你运气不好。 - Benjamin James Kippax

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