ป้ายกำกับ HTML จะไม่ทริกเกอร์อินพุตที่เกี่ยวข้องหากเมาส์ถูกย้ายขณะคลิกใน Firefox


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 เมื่อคุณเลื่อนเคอร์เซอร์ไปมาระหว่างmousedownและกับmouseupเหตุการณ์อินพุทยังคงได้รับการทริกเกอร์ในขณะที่ใน Firefox ช่องทำเครื่องหมายจะไม่เปลี่ยนสถานะ

มีวิธีแก้ไขปัญหานี้หรือไม่? (โดยไม่ใช้ผู้ฟังเหตุการณ์ JavaScript)

รุ่น Firefox: 69.0.3 (64-bit)

ชุดเต็มของการกระทำเมื่อใช้โครเมี่ยม

  1. กดปุ่มบนฉลาก
  2. เลื่อนเคอร์เซอร์ไปรอบ ๆ (แม้ด้านนอกฉลาก) ในขณะที่ยังกดปุ่มค้างไว้
  3. กลับเคอร์เซอร์กลับไปที่ฉลาก
  4. ปล่อยปุ่ม

ใน Chrome เมื่อคุณเลื่อนเคอร์เซอร์ระหว่างเหตุการณ์ที่มีการวางเมาส์และเมาส์วางการป้อนข้อมูลจะยังคงถูกเรียก -> มันไม่ใช่กรณีของฉันสำหรับ Chrome และไม่ควรเป็นเช่นนั้น a click = mousedown + mouseup .. เกี่ยวข้องกับแนวคิดบางอย่าง: stackoverflow.com/a/51451218/8620333
Temani Afif

@TemaniAfif เป็นกรณีของคุณใน Chrome หรือไม่ (ตั้งแต่ฉันได้ชี้แจงว่าฉันกำลังทำอะไรอยู่) หรือยังไม่เปลี่ยนสถานะของช่องทำเครื่องหมาย?
Nick zoum

1
หากเราเก็บเม้าส์ไว้เหนือป้ายกำกับมันก็โอเค การย้ายไม่ควรส่งผลกระทบต่อสิ่งนี้ดังนั้นฉันเดาว่าเรากำลังเผชิญกับข้อผิดพลาดของ Firefox
Temani Afif

2
นี่เป็นข้อผิดพลาดที่ถูกบันทึกเป็นสถานะ UNCONFIRMED ใน firfox bug portal เหตุการณ์ "A" คลิก "จะเกิดขึ้นก็ต่อเมื่อ mousedown และ mouseup อยู่ในตำแหน่งเดียวกัน" คุณสามารถตรวจสอบข้อผิดพลาด url: bugzilla.mozilla.org/show_bug cgi? id = 319347
Jadli

1
@TemaniAfif ฉันยอมรับการเลื่อนเคอร์เซอร์แม้1pxจะเป็นการหยุดการตอบสนอง
Nick zoum

คำตอบ:


3

บทนำ

แม้ว่าฉันจะระบุไว้เฉพาะในคำถามที่คำตอบไม่ควรเกี่ยวข้องกับ JavaScript แต่คำตอบทั้งหมดนั้นใช้ได้กับ JavaScript
เนื่องจากนี่เป็นข้อผิดพลาดของ Firefox และคำตอบส่วนใหญ่ที่ส่งมา ณ จุดนี้จะทำให้ฉันต้องเปลี่ยนรหัสที่เหลือด้วยฉันจึงตัดสินใจสร้างสคริปต์ที่สามารถเรียกใช้ครั้งเดียวจะจัดการกับป้ายกำกับทั้งหมดโดยไม่คำนึงว่าเมื่อใด พวกเขาจะถูกเพิ่มเข้าไปใน 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ใน0msเพื่อเพิ่มforแอตทริบิวต์กลับคืนเมื่อเหตุการณ์เกิดขึ้น ซึ่งหมายความว่าevent.preventDefaultไม่จำเป็นต้องถูกเรียกดังนั้นจึงไม่มีการกระทำ / เหตุการณ์อื่น ๆ ที่จะถูกยกเลิก นอกจากนี้หากฉันต้องการแทนที่ฟังก์ชั่นนี้ฉันเพียงแค่ต้องเพิ่มฟังเหตุการณ์ที่เรียกEvent#preventDefaultหรือลบforคุณลักษณะ

manageLabel

ฟังก์ชั่นmanageLabelยอมรับการตรวจสอบป้ายกำกับว่ามีการเพิ่มผู้ฟังเหตุการณ์แล้วเพื่อหลีกเลี่ยงการเพิ่มอีกครั้งเพิ่มผู้ฟังหากยังไม่ได้เพิ่มและเพิ่มลงในรายการป้ายกำกับที่ได้รับการจัดการ

onLoad

ฟังก์ชันonLoadจำเป็นต้องได้รับการเรียกเมื่อเพจได้รับการโหลดเพื่อให้manageLabelสามารถเรียกใช้ฟังก์ชันสำหรับป้ายกำกับทั้งหมดใน DOM ในขณะนั้น ฟังก์ชั่นนี้ยังใช้MutationObserverเพื่อตรวจจับฉลากที่เพิ่มเข้ามาหลังจากที่โหลดเสร็จสิ้นแล้ว (และสคริปต์ทำงานแล้ว)

รหัสที่ปรากฏข้างต้นถูกปรับให้เหมาะสมโดยมาร์ตินบาร์เกอร์


โค้ดของคุณเป็นแบบไม่
Barkermn01

มันไม่ได้ลบการตรวจสอบมันแทนที่ด้วยการHTMLLabelElementตรวจสอบแทนทั้ง HTMLElement และตรวจสอบว่า tagName เป็นฉลาก
Barkermn01

ฉลาด แต่ซับซ้อนอย่างหนาแน่น
Steve Tomlin

2

ฉันรู้ว่าคุณไม่ต้องการให้ผู้ฟังเหตุการณ์ JS แต่ฉันคิดว่าคุณหมายถึงการระบุการเคลื่อนไหวที่ไม่เพียง แต่ใช้ mousedown แทนการคลิก (mousedown ตามด้วย mouseup)

ในขณะที่นี่เป็นข้อผิดพลาดที่รู้จักกันใน Firefox คุณสามารถหลีกเลี่ยงได้โดยใช้เหตุการณ์ mousedown

ฉันต้องเปลี่ยนรหัสของคุณให้เป็นรหัสที่ถูกต้องต้องเริ่มต้นด้วยตัวอักษร

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 วินาทีเร็วพอที่ผู้ใช้จะไม่สังเกตเห็นมันและช้าพอที่เบราว์เซอร์จะทำงาน intime ถ้ามันจะทำในสิ่งที่ควรจะเป็น ดีกว่าไหม
Barkermn01

0

ไม่ดูเหมือนนี่เป็นข้อบกพร่องของ Firefox และไม่ใช่ปัญหาเกี่ยวกับรหัสของคุณ ฉันไม่เชื่อว่ามีวิธีแก้ปัญหา css สำหรับพฤติกรรมนี้

คุณอาจจะสามารถรายงานไปยัง Mozilla และรับการแก้ไขปัญหา แต่ฉันจะไม่พึ่งพาที่ https://bugzilla.mozilla.org/home

สำหรับวิธีแก้ปัญหาที่อาจเกิดขึ้นฉันขอแนะนำให้เรียกใช้เหตุการณ์บนการวางเมาส์แทน


0

หากไม่มี javascript เมื่อคุณคลิกป้ายกำกับที่มีค่า "สำหรับ" เหมือนกับค่า "id" ของอินพุตข้อมูลนั้นจะได้รับการคลิก แต่สิ่งนี้จะไม่สอดคล้องกันในเบราว์เซอร์

หากเบราว์เซอร์ปฏิบัติตามข้างต้นเหตุการณ์การคลิกจาวาสคริปต์ของคุณจะยกเลิกผลกระทบซึ่งจะเป็นการไม่ทำอะไรเลย

วิธีแก้ปัญหา

เพื่อให้มีความสอดคล้องกันระหว่างเบราว์เซอร์คุณสามารถใช้กลยุทธ์ที่แตกต่างกัน: Onload เปลี่ยนแปลงคุณลักษณะทั้งหมด 'สำหรับ' เป็น 'สำหรับ' ข้อมูลสำหรับ 'แบบไดนามิกดังนั้นจึงไม่มีผลต่อเบราว์เซอร์ดั้งเดิม จากนั้นคุณสามารถใช้เหตุการณ์การคลิกของคุณกับแต่ละป้ายกำกับ

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

มันเป็นเพียงวิธีการที่ฉันเห็นซึ่งเป็นเบราว์เซอร์ x อีกเล็กน้อย เลือกสิ่งที่คุณต้องการ
Steve Tomlin

-2

การแนบเหตุการณ์เข้ากับเอกสารและกำหนดเป้าหมายองค์ประกอบที่คุณต้องการเพื่อจัดเรียงปัญหานี้

$ (เอกสาร) .on ('คลิก', '. รายการ', ฟังก์ชัน (เหตุการณ์) {});

จากการอ่านหัวข้อนี้ในอดีตมันเป็นเรื่องของ Firefox ที่จะเข้าใจการกระทำของคุณในความพยายามที่จะลากองค์ประกอบอย่างไรก็ตามเนื่องจากการเลือกของผู้ใช้นั้นไม่มีเลยมันแค่ป้องกันพฤติกรรมเริ่มต้น

สิ่งนี้มีพื้นฐานมาจากความรู้ที่ จำกัด แต่ดูเหมือนว่าจะเป็นข้อบกพร่อง / มุมแหลมที่เป็นที่รู้จักและมีบางบทความที่ลอยอยู่รอบ ๆ การสนับสนุนสิ่งนี้


without using JavaScript event listenersฉันอย่างชัดเจนกล่าวว่าในคำถาม
Nick zoum

@nickzoum เนื่องจากเป็นข้อบกพร่องที่รู้จักใน Firefox ฉันคิดว่าคุณโชคไม่ดี
Benjamin James Kippax
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.