简体   繁体   中英

HTML Label doesn't trigger the respective input if the mouse gets moved while clicking in Firefox

In the following example, when you click on the label, the input changes state.

 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>

In Chrome, when you move the cursor between the mousedown and mouseup events the input still gets triggered, whereas in Firefox the checkbox doesn't change state.

Is there a way to fix this? (without using JavaScript event listeners)

Firefox version: 69.0.3 (64-bit)

Full set of actions when using chrome.

  1. Press the button over the label
  2. Move the cursor around (even outside the label) while still holding the button
  3. Return the cursor back to the label
  4. Release the button

Introduction

Although I specifically stated in the question that the answer shouldn't involve JavaScript, all the answers worked with JavaScript.
Since this seems to be a Firefox bug and most of the answers submitted at this point would require me to also alter the rest of my code, I decided to create a script that can be run once, will deal with all the labels regardless of when they are added to the dom and will have the least impact on my other scripts.

Solution - Example

 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>

Explanation

onLabelClick

The function onLabelClick needs to get called whenever a label gets clicked, it will check if the label has a corresponding input element. If it does, it will trigger it, remove the for attribute of the label so that the browsers don't have the bug won't re-trigger it and then use a setTimeout of 0ms to add the for attribute back once the event has bubbled up. This means event.preventDefault doesn't have to get called and thus no other actions/events will get cancelled. Also if I need to override this function I just have to add an event-listener that calls Event#preventDefault or removes the for attribute.

manageLabel

The function manageLabel accepts a label checks if it has already been added an event listener to avoid re-adding it, adds the listener if it hasn't already been added, and adds it to the list of labels have been managed.

onLoad

The function onLoad needs to get called when the page gets loaded so that the function manageLabel can be called for all the labels on the DOM at that moment. The function also uses a MutationObserver to catch any labels that get added, after the load has been fired (and the script has been run).

The code displayed above was optimized by Martin Barker .

I know you did not want JS Event listeners, but im thinking you mean to identify the movement this does not but is using mousedown instead of click (mousedown followed by mouseup).

While this is a known bug in Firefox you could get around it by using the mousedown event

I have had to change your id to be a valid one id's must start with a character

 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>

No. This looks like a firefox bug and not an issue with your code. I don't believe there is a css workaround for this behavior.

You may be able to report it to Mozilla and get the issue fixed, but I wouldn't rely on that. https://bugzilla.mozilla.org/home

For a potential workaround I would suggested triggering the event on mouseup instead.

Without javascript, when you click the label that has its "for" value the same as an inputs "id" value then the input gets clicked, but this is not consistent amongst browsers.

If a browser does follow the above, then your javascript click event will cancel out the effect, which ends up doing nothing.

A solution

To have consistency amongst browsers you could adopt a different strategy: Onload dynamically change all the attribute 'for' for 'data-for', so it nulls the original browser affect. Then you can apply your click event to each label.

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

Sorry for posting this as an answer (my reputation is under 50), I think that this' just that way that firefox works maybe?

Attaching the event to the document and targeting the element you require in there ought to sort this issue.

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

From reading on this topic in the past, it's down to Firefox understanding your action as an attempt to drag the element however, since user select is none, it just prevents the default behaviour.

This is based on fairly limited knowledge but it seems to be a known bug/quirk and there is a few articles floating around supporting this.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM