Etykieta HTML nie wyzwala odpowiedniego wejścia, jeśli mysz zostanie poruszona podczas klikania w przeglądarce Firefox

13

W poniższym przykładzie kliknięcie etykiety powoduje zmianę stanu danych wejściowych.

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>

W Chrome, gdy przesuwasz kursor między zdarzeniami mousedowni mouseup, dane wejściowe są nadal wyzwalane, podczas gdy w Firefox pole wyboru nie zmienia stanu.

Czy istnieje sposób, aby to naprawić? (bez użycia detektorów zdarzeń JavaScript)

Wersja Firefox: 69.0.3 (64-bit)

Pełny zestaw działań podczas korzystania z chrome.

  1. Naciśnij przycisk na etykiecie
  2. Przesuń kursor wokół (nawet poza etykietę), wciąż trzymając przycisk
  3. Wróć kursor z powrotem do etykiety
  4. Zwolnij przycisk
nick zoum
źródło
W Chrome, kiedy przesuwasz kursor między zdarzeniami mousedown i mouseup, dane wejściowe są nadal wyzwalane -> w przypadku Chrome nie jest tak i ot tak nie powinno być. kliknięcie = mousedown + najechanie myszką ... powiązane z niektórymi pomysłami: stackoverflow.com/a/51451218/8620333
Temani Afif
@TemaniAfif Czy teraz sprawa dotyczy Ciebie na Chrome? (ponieważ wyjaśniłem, co robię). Czy nadal nie zmienia stanu pola wyboru?
nick zoum
1
Jeśli zatrzymamy wskaźnik myszy na etykiecie, to będzie w porządku. ruch nie powinien na to wpłynąć, więc myślę, że mamy do czynienia z błędem Firefoksa
Temani Afif
2
jest to błąd, który jest rejestrowany jako status NIEPOTWIERDZONY w portalu błędów firfox. Zdarzenie „kliknięcie” powinno wystąpić tylko wtedy, gdy mousedown i mouseup znajdowały się w tej samej lokalizacji. Możesz tam sprawdzić adres URL błędu: bugzilla.mozilla.org/show_bug. cgi? id = 319347
Jadli
1
@TemaniAfif Zgadzam się, przesunięcie kursora nawet 1pxprzerwie interakcję.
nick zoum

Odpowiedzi:

3

Wprowadzenie

Chociaż wyraźnie stwierdziłem w pytaniu, że odpowiedź nie powinna obejmować JavaScript, wszystkie odpowiedzi działały z JavaScript.
Ponieważ wydaje się, że jest to błąd przeglądarki Firefox, a większość odpowiedzi przesłanych w tym momencie wymagałaby również zmiany reszty mojego kodu, postanowiłem utworzyć skrypt, który można uruchomić raz, poradzi sobie ze wszystkimi etykietami niezależnie od tego, kiedy są dodawane do domeny i będą miały najmniejszy wpływ na moje inne skrypty.

Rozwiązanie - przykład

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>

Wyjaśnienie

onLabelClick

Funkcja onLabelClickmusi być wywoływana za każdym razem, gdy kliknięta zostanie etykieta, sprawdzi, czy etykieta ma odpowiedni element wejściowy. Jeśli tak, to będzie go wyzwolić, usunąć foratrybut etykiety tak, że przeglądarki nie ma błędu nie będzie re-trigger go, a następnie użyć setTimeoutw 0mscelu dodania forplecy atrybutu raz impreza przepuszcza się w górę. Oznacza to, event.preventDefaultże nie trzeba dzwonić, dlatego żadne inne działania / zdarzenia nie zostaną anulowane. Również jeśli muszę zastąpić tę funkcję, muszę tylko dodać detektor zdarzeń, który wywołuje Event#preventDefaultlub usuwa foratrybut.

manageLabel

FunkcjamanageLabelakceptuje etykietę sprawdza, czy został już dodany detektor zdarzeń, aby uniknąć ponownego dodania go, dodaje detektor, jeśli nie został jeszcze dodany, i dodaje go do listy etykiet, którymi zarządzano.

onLoad

Funkcja onLoadmusi zostać wywołana, gdy strona zostanie załadowana, aby manageLabelmożna było wywołać funkcję dla wszystkich etykiet w DOM w tym momencie. Funkcja używa także MutationObserver do przechwytywania dodawanych etykiet po uruchomieniu obciążenia (i uruchomieniu skryptu).

Powyższy kod został zoptymalizowany przez Martina Barkera .

nick zoum
źródło
Twój kod jest trochę niezoptymalizowany, przeprowadzając pewne kontrole, których nie musi, i kilka kosztownych cykli procesora. Zoptymalizowałem go dla ciebie na pastebin.com/FDXwQL1d
Barkermn01
Nie usuwa czeku, zastępuje go HTMLLabelElementczekiem zamiast HTMLElement i sprawdzeniem, czy tagName to etykieta
Barkermn01,
sprytne, ale bardzo skomplikowane
Steve Tomlin
2

Wiem, że nie chciałeś nasłuchiwać zdarzeń JS, ale myślę, że chcesz zidentyfikować ruch, który nie polega na tym, że używa mousedown zamiast klikania (mousedown, a następnie upuszczanie myszy).

Chociaż jest to znany błąd w przeglądarce Firefox, można go obejść za pomocą zdarzenia mousedown

Musiałem zmienić Twój identyfikator, aby był prawidłowy. Identyfikator musi zaczynać się od znaku

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>

Barkermn01
źródło
Problem polega na tym, że będę musiał uruchomić ten skrypt (przy użyciu querySelectorAll) podczas ładowania strony, a następnie za każdym razem, gdy etykieta zostanie dodana do domeny. Może to również zepsuć wszystkie detektory zdarzeń myszy dodane do etykiet lub domeny w ogóle, gdy używasz, Event#preventDefaultaby uniknąć podwójnego klikania w przeglądarkach, które nie mają tego błędu. Wreszcie, użycie clickzdarzenia byłoby lepsze, ponieważ miałoby to tę samą interakcję, co zamierzone działanie. Poza tym to najlepsza jak dotąd odpowiedź.
nick zoum
@nickzoum zaktualizowałem kod, aby naprawić preventDefault()problem, więc działa, sprawdzając, czy przeglądarka go zmieniła, jeśli nie zmieni, po 150 ms wystarczająco szybko, aby użytkownik nie zauważył, i wystarczająco wolno, aby przeglądarka działała intime, jeśli zrobi to, co powinien. czy to jest lepsze?
Barkermn01,
0

Nie. To wygląda jak błąd Firefoksa, a nie problem z twoim kodem. Nie wierzę, że istnieje takie obejście css dla tego zachowania.

Możesz być w stanie zgłosić to Mozilli i naprawić problem, ale nie polegałbym na tym. https://bugzilla.mozilla.org/home

Dla potencjalnego obejścia zasugerowałem zamiast tego wywołanie zdarzenia po najechaniu myszką.

Garet Webster
źródło
0

Bez javascript, po kliknięciu etykiety, której wartość „for” jest taka sama jak wartość „id” wejściowego, dane wejściowe są klikane, ale nie jest to spójne wśród przeglądarek.

Jeśli przeglądarka postępuje zgodnie z powyższym, to zdarzenie kliknięcia w javascript anuluje efekt, co w efekcie nie robi nic.

Rozwiązanie

Aby zachować spójność między przeglądarkami, możesz zastosować inną strategię: Onload dynamicznie zmienia wszystkie atrybuty „for” dla „data-for”, więc unieważnia oryginalny efekt przeglądarki. Następnie możesz zastosować zdarzenie kliknięcia do każdej etykiety.

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();
  }
});
Steve Tomlin
źródło
document.attachEvent("onreadystatechange",Jestem po prostu zintegrowany, jak to się stało, a nie document.addEventListener("DOMContentLoaded",?
Barkermn01,
To była tylko metoda, którą widziałem, czyli trochę więcej X-przeglądarki. Wybierz, który wolisz.
Steve Tomlin
-2

Dołączenie zdarzenia do dokumentu i ukierunkowanie na wymagany element powinno rozwiązać ten problem.

$ (Dokument) .on („kliknięcie”, „.item”, funkcja (zdarzenie) {});

Od czytania tego tematu w przeszłości, Firefox rozumie twoje działanie jako próbę przeciągnięcia elementu, ponieważ wybór użytkownika nie ma, po prostu uniemożliwia zachowanie domyślne.

Opiera się to na dość ograniczonej wiedzy, ale wydaje się, że jest to znany błąd / dziwactwo i jest kilka artykułów na ten temat.

Benjamin James Kippax
źródło
Ja wyraźnie powiedziałem w pytaniu without using JavaScript event listeners.
nick zoum
@nickzoum, ponieważ jest to znany błąd w Firefoksie, myślę, że nie masz szczęścia.
Benjamin James Kippax