Zdarzenie onchange na typie wejściowym = zakres nie jest wyzwalany w firefoxie podczas przeciągania

252

Kiedy grałem <input type="range">, Firefox wyzwala zdarzenie onchange tylko wtedy, gdy upuszczamy suwak do nowej pozycji, w której Chrome i inni uruchamiają zdarzenia onchange, gdy suwak jest przeciągany.

Jak mogę to zrobić podczas przeciągania w przeglądarce Firefox?

function showVal(newVal){
    document.getElementById("valBox").innerHTML=newVal;
}
<span id="valBox"></span>
<input type="range" min="5" max="10" step="1" onchange="showVal(this.value)">

Prasanth KC
źródło
4
Jeśli element zakresu ma fokus, możesz przesuwać suwak za pomocą klawiszy strzałek. I w tym przypadku również onchangenie strzela. Znalazłem to pytanie podczas rozwiązywania tego problemu.
Sildoreth,

Odpowiedzi:

452

Najwyraźniej Chrome i Safari są niepoprawne: onchangepowinny być uruchamiane tylko po zwolnieniu myszy przez użytkownika. Aby otrzymywać ciągłe aktualizacje, należy skorzystać ze oninputzdarzenia, które przechwytuje aktualizacje na żywo w Firefox, Safari i Chrome, zarówno z myszy, jak i klawiatury.

Jednak oninputnie jest obsługiwany w IE10, więc najlepszym rozwiązaniem jest połączenie dwóch programów obsługi zdarzeń, takich jak to:

<span id="valBox"></span>
<input type="range" min="5" max="10" step="1" 
   oninput="showVal(this.value)" onchange="showVal(this.value)">

Sprawdź ten wątek Bugzilli, aby uzyskać więcej informacji.

Frederik
źródło
113
Lub $("#myelement").on("input change", function() { doSomething(); });z jQuery.
Alex Vang
19
Zauważ tylko, że dzięki temu rozwiązaniu w chrome otrzymasz dwa wywołania do modułu obsługi (jedno na zdarzenie), więc jeśli ci na tym zależy, musisz się przed nim chronić.
Jaime
3
Miałem problemy ze zdarzeniem „zmiana”, które nie uruchamiało się w mobilnym chrome (v34), ale dodanie „danych wejściowych” do równania naprawiło to dla mnie, nie wywołując przy tym żadnych szkodliwych efektów.
brins0
7
@Jaime: „ Jeśli ci na tym zależy, musisz się przed tym zabezpieczyć. ” Jak mogę to zrobić, proszę?
2
Niestety onchange()nie działa w internecie mobilnym, takim jak Android Chromei iOS Safari. Wszelkie alternatywne sugestie?
Alston,
41

PODSUMOWANIE:

Zapewniam tutaj możliwość korzystania z przeglądarki internetowej i mobilnej bez jQuery do konsekwentnego reagowania na interakcje zakresu / suwaka, co jest niemożliwe w obecnych przeglądarkach. Zasadniczo zmusza wszystkie przeglądarki do emulacji on("change"...zdarzenia IE11 dla ich zdarzeń on("change"...lub on("input"...zdarzeń. Nowa funkcja to ...

function onRangeChange(r,f) {
  var n,c,m;
  r.addEventListener("input",function(e){n=1;c=e.target.value;if(c!=m)f(e);m=c;});
  r.addEventListener("change",function(e){if(!n)f(e);});
}

... gdzie rjest twój element wejściowy zakresu i ftwój słuchacz. Słuchacz zostanie wywołany po każdej interakcji, która zmienia wartość zakresu / suwaka, ale nie po interakcjach, które nie zmieniają tej wartości, w tym początkowych interakcji myszy lub dotyku w bieżącej pozycji suwaka lub po zejściu z dowolnego końca suwaka.

Problem:

Na początku czerwca 2016 r. Różne przeglądarki różnią się pod względem reakcji na użycie zakresu / suwaka. Pięć scenariuszy jest istotnych:

  1. początkowe przesuwanie myszą w dół (lub touch-start) w bieżącej pozycji suwaka
  2. początkowe przesuwanie myszą w dół (lub touch-start) w nowej pozycji suwaka
  3. każdy kolejny ruch myszy (lub dotyku) po 1 lub 2 wzdłuż suwaka
  4. każdy kolejny ruch myszy (lub dotyku) po 1 lub 2 za dowolnym końcem suwaka
  5. końcowe najechanie myszą (lub touch-end)

Poniższa tabela pokazuje, w jaki sposób różnią się zachowanie co najmniej trzech różnych przeglądarek na komputery, w odniesieniu do których z powyższych scenariuszy odpowiadają:

tabela pokazująca różnice w przeglądarce w odniesieniu do zdarzeń, na które reagują i kiedy

Rozwiązanie:

Ta onRangeChangefunkcja zapewnia spójną i przewidywalną reakcję różnych przeglądarek na interakcje zakresu / suwaka. Wymusza zachowanie wszystkich przeglądarek zgodnie z poniższą tabelą:

tabela pokazująca zachowanie wszystkich przeglądarek korzystających z proponowanego rozwiązania

W IE11 kod zasadniczo pozwala wszystkim działać zgodnie ze status quo, tzn. Pozwala "change"zdarzeniu funkcjonować w standardowy sposób, a "input"zdarzenie jest nieistotne, ponieważ i tak nigdy się nie uruchamia. W innych przeglądarkach "change"zdarzenie jest skutecznie wyciszane (aby zapobiec uruchamianiu dodatkowych, a czasem niezbyt widocznych zdarzeń). Ponadto "input"zdarzenie uruchamia detektor tylko wtedy, gdy zmienia się wartość zakresu / suwaka. W przypadku niektórych przeglądarek (np. Firefox) dzieje się tak, ponieważ słuchacz jest skutecznie wyciszony w scenariuszach 1, 4 i 5 z powyższej listy.

(Jeśli naprawdę potrzebujesz aktywacji słuchacza w jednym ze scenariuszy 1, 4 i / lub 5, możesz spróbować włączyć "mousedown"/ "touchstart", "mousemove"/ "touchmove"i / lub "mouseup"/ "touchend"zdarzenia. Takie rozwiązanie jest poza zakresem tej odpowiedzi).

Funkcjonalność w przeglądarkach mobilnych:

Testowałem ten kod w przeglądarkach komputerowych, ale nie w żadnych przeglądarkach mobilnych. Jednak w innej odpowiedzi na tej stronie MBourne pokazał, że moje rozwiązanie tutaj „... wydaje się działać w każdej przeglądarce, jaką mogłem znaleźć (Win Desktop: IE, Chrome, Opera, FF; Android Chrome, Opera i FF, iOS Safari) „ . (Dzięki MBourne.)

Stosowanie:

Aby skorzystać z tego rozwiązania, włącz onRangeChangefunkcję z powyższego podsumowania (uproszczonego / zminimalizowanego) lub fragment kodu demonstracyjnego poniżej (funkcjonalnie identyczny, ale bardziej zrozumiały) we własnym kodzie. Wywołaj go w następujący sposób:

onRangeChange(myRangeInputElmt, myListener);

gdzie myRangeInputElmtjest pożądany <input type="range">element DOM i myListenerjest funkcją nasłuchiwania / obsługi, która ma być wywoływana po "change"zdarzeniach podobnych do tego.

W razie potrzeby Twój detektor może być pozbawiony parametrów lub może użyć eventparametru, tzn. Zadziała dowolna z poniższych opcji, w zależności od potrzeb:

var myListener = function() {...

lub

var myListener = function(evt) {...

(Usunięcie detektora zdarzeń z inputelementu (np. Użycie removeEventListener) nie jest uwzględnione w tej odpowiedzi.)

Opis wersji demo:

W poniższym fragmencie kodu funkcja onRangeChangezapewnia uniwersalne rozwiązanie. Reszta kodu jest po prostu przykładem demonstrującym jego użycie. Każda zmienna, która zaczyna się od, nie my...ma znaczenia dla uniwersalnego rozwiązania i jest obecna tylko na potrzeby wersji demonstracyjnej.

Pokazy demo wartość zakres / suwak, a także ile razy średnia "change", "input"i niestandardowych "onRangeChange"zdarzeń mają opalane (wiersze A, B i C, odpowiednio). Podczas korzystania z tego fragmentu w różnych przeglądarkach podczas interakcji z suwakiem zakresu / suwaka zwróć uwagę na następujące kwestie:

  • W IE11 wartości w wierszach A i C zmieniają się w scenariuszach 2 i 3 powyżej, podczas gdy wiersz B nigdy się nie zmienia.
  • W Chrome i Safari zarówno wartości w wierszach B, jak i C zmieniają się w scenariuszach 2 i 3, podczas gdy wiersz A zmienia się tylko w scenariuszu 5.
  • W przeglądarce Firefox wartość w wierszu A zmienia się tylko dla scenariusza 5, wiersz B zmienia się dla wszystkich pięciu scenariuszy, a wiersz C zmienia się tylko dla scenariuszy 2 i 3.
  • We wszystkich powyższych przeglądarkach zmiany w wierszu C (proponowane rozwiązanie) są identyczne, tj. Tylko w scenariuszach 2 i 3.

Kod demonstracyjny:

// main function for emulating IE11's "change" event:

function onRangeChange(rangeInputElmt, listener) {

  var inputEvtHasNeverFired = true;

  var rangeValue = {current: undefined, mostRecent: undefined};
  
  rangeInputElmt.addEventListener("input", function(evt) {
    inputEvtHasNeverFired = false;
    rangeValue.current = evt.target.value;
    if (rangeValue.current !== rangeValue.mostRecent) {
      listener(evt);
    }
    rangeValue.mostRecent = rangeValue.current;
  });

  rangeInputElmt.addEventListener("change", function(evt) {
    if (inputEvtHasNeverFired) {
      listener(evt);
    }
  }); 

}

// example usage:

var myRangeInputElmt = document.querySelector("input"          );
var myRangeValPar    = document.querySelector("#rangeValPar"   );
var myNumChgEvtsCell = document.querySelector("#numChgEvtsCell");
var myNumInpEvtsCell = document.querySelector("#numInpEvtsCell");
var myNumCusEvtsCell = document.querySelector("#numCusEvtsCell");

var myNumEvts = {input: 0, change: 0, custom: 0};

var myUpdate = function() {
  myNumChgEvtsCell.innerHTML = myNumEvts["change"];
  myNumInpEvtsCell.innerHTML = myNumEvts["input" ];
  myNumCusEvtsCell.innerHTML = myNumEvts["custom"];
};

["input", "change"].forEach(function(myEvtType) {
  myRangeInputElmt.addEventListener(myEvtType,  function() {
    myNumEvts[myEvtType] += 1;
    myUpdate();
  });
});

var myListener = function(myEvt) {
  myNumEvts["custom"] += 1;
  myRangeValPar.innerHTML = "range value: " + myEvt.target.value;
  myUpdate();
};

onRangeChange(myRangeInputElmt, myListener);
table {
  border-collapse: collapse;  
}
th, td {
  text-align: left;
  border: solid black 1px;
  padding: 5px 15px;
}
<input type="range"/>
<p id="rangeValPar">range value: 50</p>
<table>
  <tr><th>row</th><th>event type                     </th><th>number of events    </th><tr>
  <tr><td>A</td><td>standard "change" events         </td><td id="numChgEvtsCell">0</td></tr>
  <tr><td>B</td><td>standard "input" events          </td><td id="numInpEvtsCell">0</td></tr>
  <tr><td>C</td><td>new custom "onRangeChange" events</td><td id="numCusEvtsCell">0</td></tr>
</table>

Kredyt:

Chociaż implementacja tutaj jest w dużej mierze moja, zainspirowała ją odpowiedź MBourne . Ta inna odpowiedź sugerowała, że ​​zdarzenia „wejściowe” i „zmieniające” mogą zostać połączone, a wynikowy kod będzie działał zarówno w przeglądarkach stacjonarnych, jak i mobilnych. Jednak kod w tej odpowiedzi powoduje, że uruchamiane są ukryte „dodatkowe” zdarzenia, co samo w sobie jest problematyczne, a wyzwalane zdarzenia różnią się między przeglądarkami, co stanowi kolejny problem. Moja implementacja rozwiązuje te problemy.

Słowa kluczowe:

JavaScript wejściowy typ zakresu suwak zdarzenia zmieniają wejście kompatybilność z przeglądarką przeglądarka stacjonarna mobilny no-jQuery

Andrew Willems
źródło
31

Publikuję to jako odpowiedź, ponieważ zasługuje na to, aby była to odpowiedź własna, a nie komentarz pod mniej użyteczną odpowiedzią. Uważam, że ta metoda jest znacznie lepsza niż zaakceptowana odpowiedź, ponieważ może przechowywać wszystkie pliki js w osobnym pliku od HTML.

Odpowiedź udzielona przez Jamreliana w jego komentarzu pod przyjętą odpowiedzią.

$("#myelement").on("input change", function() {
    //do something
});

Pamiętaj jednak o tym komentarzu Jaime'a

Zauważ tylko, że dzięki temu rozwiązaniu w chrome otrzymasz dwa wywołania do modułu obsługi (jedno na zdarzenie), więc jeśli ci na tym zależy, musisz się przed nim chronić.

Jak w nim, uruchomi się zdarzenie, gdy przestaniesz poruszać myszą, a następnie ponownie, gdy zwolnisz przycisk myszy.

Aktualizacja

W związku ze changezdarzeniem i inputzdarzeniem powodującym dwukrotne uruchomienie funkcji, nie stanowi to problemu.

Jeśli masz funkcję odpalającą input, jest bardzo mało prawdopodobne, że wystąpi problem po uruchomieniu changezdarzenia.

inputodpala szybko podczas przeciągania suwaka wprowadzania zakresu. Martwienie się o jeszcze jedno wywołanie funkcji na końcu jest jak martwienie się o pojedynczą kroplę wody w porównaniu do oceanu wody, który jest inputwydarzeniem.

Powodem nawet włączenia tego changewydarzenia jest ze względu na kompatybilność przeglądarki (głównie z IE).

Daniel Tonon
źródło
plus 1 Za jedyne rozwiązanie współpracujące z Internet Explorerem !! Czy przetestowałeś to również w innych przeglądarkach?
Jessica,
1
Jest to jQuery, więc szanse na to, że nie zadziała, są niewielkie. Nigdzie sam nie widziałem, żeby to działało. Działa nawet na urządzeniach mobilnych, co jest świetne.
Daniel Tonon,
30

AKTUALIZACJA: Pozostawiam tę odpowiedź tutaj jako przykład użycia zdarzeń myszy do interakcji zakresu / suwaka w przeglądarkach komputerowych (ale nie mobilnych). Jednak teraz napisałem też zupełnie inną i, jak sądzę, lepszą odpowiedź w innym miejscu na tej stronie, która stosuje inne podejście do zapewnienia rozwiązania tego problemu w różnych przeglądarkach na komputery i urządzenia mobilne .

Oryginalna odpowiedź:

Podsumowanie: Proste w przeglądarce rozwiązanie JavaScript (tj. Bez jQuery) umożliwiające odczyt wartości wejściowych zakresu bez użycia on('input'...i / lub on('change'...które działają niespójnie między przeglądarkami.

Na dzień dzisiejszy (koniec lutego 2016 r.) Nadal występuje niespójność przeglądarki, dlatego udostępniam nowe rozwiązanie.

Problem: Podczas korzystania z danych wejściowych zakresu, tj. Suwaka, on('input'...zapewnia stale aktualizowane wartości zakresu w komputerach Mac i Windows Firefox, Chrome i Opera, a także Mac Safari, a on('change'...raportuje tylko wartość zakresu po najechaniu myszą. Natomiast w przeglądarce Internet Explorer (v11) w on('input'...ogóle nie działa i on('change'...jest stale aktualizowana.

Zgłaszam tutaj 2 strategie uzyskiwania identycznego ciągłego raportowania wartości zakresu we wszystkich przeglądarkach za pomocą waniliowego JavaScript (tj. Bez jQuery) poprzez użycie zdarzeń mousedown, mousemove i (prawdopodobnie) mouseup.

Strategia 1: Krótsza, ale mniej wydajna

Jeśli wolisz krótszy kod niż kod bardziej wydajny, możesz użyć tego pierwszego rozwiązania, które używa myszy i myszy, ale nie myszy. Odczytuje suwak w razie potrzeby, ale nadal uruchamia się niepotrzebnie podczas zdarzeń po najechaniu myszką, nawet gdy użytkownik nie kliknął, a zatem nie przeciąga suwaka. Zasadniczo odczytuje wartość zakresu zarówno po zdarzeniu „mousedown”, jak i podczas „ruchów myszy”, nieco opóźniając każde użycie requestAnimationFrame.

var rng = document.querySelector("input");

read("mousedown");
read("mousemove");
read("keydown"); // include this to also allow keyboard control

function read(evtType) {
  rng.addEventListener(evtType, function() {
    window.requestAnimationFrame(function () {
      document.querySelector("div").innerHTML = rng.value;
      rng.setAttribute("aria-valuenow", rng.value); // include for accessibility
    });
  });
}
<div>50</div><input type="range"/>

Strategia 2: Dłuższa, ale bardziej wydajna

Jeśli potrzebujesz bardziej wydajnego kodu i tolerujesz dłuższą długość kodu, możesz skorzystać z następującego rozwiązania, które wykorzystuje mousedown, mousemove i mouseup. Odczytuje również suwak w razie potrzeby, ale odpowiednio przestaje go czytać, gdy tylko zwolniony zostanie przycisk myszy. Istotną różnicą jest to, że zaczyna nasłuchiwać „mousemove” po „mousedown” i przestaje nasłuchiwać „mousemove” po „mouseup”.

var rng = document.querySelector("input");

var listener = function() {
  window.requestAnimationFrame(function() {
    document.querySelector("div").innerHTML = rng.value;
  });
};

rng.addEventListener("mousedown", function() {
  listener();
  rng.addEventListener("mousemove", listener);
});
rng.addEventListener("mouseup", function() {
  rng.removeEventListener("mousemove", listener);
});

// include the following line to maintain accessibility
// by allowing the listener to also be fired for
// appropriate keyboard events
rng.addEventListener("keydown", listener);
<div>50</div><input type="range"/>

Demo: Pełniejsze wyjaśnienie potrzeby i wdrożenia powyższych obejść

Poniższy kod pełniej pokazuje wiele aspektów tej strategii. Objaśnienia są osadzone w demonstracji:

Andrew Willems
źródło
Naprawdę dobre informacje. Być może kiedyś dodasz jakieś zdarzenia dotykowe? touchmovepowinno wystarczyć i myślę, że można to traktować tak jak mousemovew tym przypadku.
nick
@nick, proszę zobaczyć moją inną odpowiedź na tej stronie, aby znaleźć inne rozwiązanie zapewniające funkcjonalność przeglądarki mobilnej.
Andrew Willems,
To nie jest dostępna odpowiedź, ponieważ nawigacja za pomocą klawiatury nie uruchamia wydarzeń, ponieważ wszystkie są skoncentrowane na myszy
LocalPCGuy
@LocalPCGuy, masz rację. Moja odpowiedź złamała wbudowane sterowanie suwakami za pomocą klawiatury. Zaktualizowałem kod, aby przywrócić kontrolę nad klawiaturą. Jeśli znasz inne sposoby, w jaki mój kod psuje wbudowaną dostępność, daj mi znać.
Andrew Willems,
7

Rozwiązania Andrew Willema nie są kompatybilne z urządzeniami mobilnymi.

Oto modyfikacja jego drugiego rozwiązania, które działa w Edge, IE, Opera, FF, Chrome, iOS Safari i odpowiednikach mobilnych (które mogłem przetestować):

Aktualizacja 1: Usunięto część „requestAnimationFrame”, ponieważ zgadzam się, że nie jest to konieczne:

var listener = function() {
  // do whatever
};

slider1.addEventListener("input", function() {
  listener();
  slider1.addEventListener("change", listener);
});
slider1.addEventListener("change", function() {
  listener();
  slider1.removeEventListener("input", listener);
}); 

Aktualizacja 2: Odpowiedź na zaktualizowaną odpowiedź Andrew z 2 czerwca 2016 r .:

Dzięki, Andrew - wydaje się, że działa w każdej przeglądarce, jaką mogłem znaleźć (Win Desktop: IE, Chrome, Opera, FF; Android Chrome, Opera i FF, iOS Safari).

Aktualizacja 3: rozwiązanie if („oninput in slider)

Poniższe wydaje się działać we wszystkich powyższych przeglądarkach. (Nie mogę teraz znaleźć oryginalnego źródła.) Używałem tego, ale potem nie udało się to w IE, więc zacząłem szukać innego, stąd trafiłem tutaj.

if ("oninput" in slider1) {
    slider1.addEventListener("input", function () {
        // do whatever;
    }, false);
}

Ale zanim sprawdziłem twoje rozwiązanie, zauważyłem, że to znowu działa w IE - być może był jakiś inny konflikt.

MBourne
źródło
1
Potwierdziłem, że twoje rozwiązanie działa w dwóch różnych przeglądarkach stacjonarnych: Mac Firefox i Windows IE11. Osobiście nie byłem w stanie sprawdzić żadnych możliwości mobilnych. Ale to wygląda na świetną aktualizację mojej odpowiedzi, która rozszerza ją na urządzenia mobilne. (Zakładam, że „wejście” i „zmiana” akceptują zarówno wejście myszy w systemie stacjonarnym, jak i dotykowe w systemie mobilnym.) Bardzo fajna praca, która powinna być szeroko stosowana i warta uznania i wdrożenia!
Andrew Willems,
1
Po dalszych kopaniach zdałem sobie sprawę, że twoje rozwiązanie ma kilka wad. Po pierwsze, uważam, że requestAnimationFrame nie jest konieczne. Po drugie, kod uruchamia dodatkowe zdarzenia (co najmniej 3 zdarzenia po np. Najechaniu myszą w niektórych przeglądarkach). Po trzecie, dokładne uruchomione zdarzenia różnią się w zależności od przeglądarki. W ten sposób podałem osobną odpowiedź na podstawie twojego początkowego pomysłu użycia kombinacji zdarzeń „wejściowych” i „zmianowych”, dając ci uznanie za oryginalną inspirację.
Andrew Willems,
2

Aby uzyskać dobre zachowanie w różnych przeglądarkach i zrozumiały kod, najlepiej jest użyć atrybutu onchange w połączeniu z formularzem:

function showVal(){
  valBox.innerHTML = inVal.value;

}
<form onchange="showVal()">
  <input type="range" min="5" max="10" step="1" id="inVal">
  </form>

<span id="valBox">
  </span>

To samo przy użyciu oninput , wartość jest zmieniana bezpośrednio.

function showVal(){
  valBox.innerHTML = inVal.value;

}
<form oninput="showVal()">
  <input type="range" min="5" max="10" step="1" id="inVal">
  </form>

<span id="valBox">
  </span>

NVRM
źródło
0

Jeszcze inne podejście - wystarczy ustawić flagę na elemencie sygnalizującym, który typ zdarzenia powinien zostać obsłużony:

function setRangeValueChangeHandler(rangeElement, handler) {
    rangeElement.oninput = (event) => {
        handler(event);
        // Save flag that we are using onInput in current browser
        event.target.onInputHasBeenCalled = true;
    };

    rangeElement.onchange = (event) => {
        // Call only if we are not using onInput in current browser
        if (!event.target.onInputHasBeenCalled) {
            handler(event);
        }
    };
}
Nikita
źródło
-3

Możesz użyć zdarzenia JavaScript „ondrag” do ciągłego uruchamiania. Jest lepszy niż „wkład” z następujących powodów:

  1. Obsługa przeglądarki.

  2. Może odróżnić zdarzenie „ondrag” i „zmień”. „wejście” odpala dla przeciągania i zmiany.

W jQuery:

$('#sample').on('drag',function(e){

});

Odniesienie: http://www.w3schools.com/TAgs/ev_ondrag.asp

Szejk
źródło