Ein Änderungsereignis für Eingabetyp = Bereich wird in Firefox beim Ziehen nicht ausgelöst

252

Wenn ich mit gespielt habe <input type="range">, löst Firefox ein Onchange-Ereignis nur aus, wenn wir den Schieberegler an einer neuen Position ablegen, an der Chrome und andere Onchange-Ereignisse auslösen, während der Slider gezogen wird.

Wie kann ich dies beim Ziehen in Firefox erreichen?

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
quelle
4
Wenn das Bereichselement den Fokus hat, können Sie den Schieberegler mit den Pfeiltasten bewegen. Und auch in diesem Fall onchangefeuert der nicht. Bei der Fehlerbehebung dieses Problems habe ich diese Frage gefunden.
Sildoreth

Antworten:

452

Anscheinend sind Chrome und Safari falsch: onchangesollten nur ausgelöst werden, wenn der Benutzer die Maus loslässt. Um fortlaufende Updates zu erhalten, sollten Sie das oninputEreignis verwenden, mit dem Live-Updates in Firefox, Safari und Chrome sowohl über die Maus als auch über die Tastatur erfasst werden.

Allerdings oninputwird nicht in IE10 unterstützt, so dass Sie am besten die beiden Event - Handler, wie diese zu kombinieren:

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

Weitere Informationen finden Sie in diesem Bugzilla-Thread .

Frederik
quelle
113
Oder $("#myelement").on("input change", function() { doSomething(); });mit jQuery.
Alex Vang
19
Beachten Sie nur, dass Sie mit dieser Lösung in Chrome zwei Anrufe an den Handler erhalten (einen pro Ereignis). Wenn Sie sich darum kümmern, müssen Sie sich dagegen schützen.
Jaime
3
Ich hatte Probleme mit dem 'change'-Ereignis, das nicht in Mobile Chrome (v34) ausgelöst wurde, aber das Hinzufügen von' input 'in die Gleichung hat es für mich behoben, während es an anderer Stelle keine nachteiligen Auswirkungen hatte.
Brins0
7
@Jaime: " Wenn dir das wichtig ist, musst du dich davor schützen. " Wie kann ich das bitte machen?
2
Funktioniert leider onchange()nicht im mobilen Web wie Android Chromeund iOS Safari. Irgendein alternativer Vorschlag?
Alston
41

ZUSAMMENFASSUNG:

Ich biete hier eine browserfreie Desktop- und Mobilfunktion ohne jQuery, um konsistent auf Interaktionen zwischen Bereich und Schieberegler zu reagieren, was in aktuellen Browsern nicht möglich ist. Es zwingt im Wesentlichen alle Browser, das IE11- on("change"...Ereignis entweder für ihre on("change"...oder für on("input"...Ereignisse zu emulieren . Die neue Funktion ist ...

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

... wo rist Ihr Bereichseingabeelement und wo fist Ihr Listener? Der Listener wird nach jeder Interaktion aufgerufen, die den Bereich / Schiebereglerwert ändert, jedoch nicht nach Interaktionen, die diesen Wert nicht ändern, einschließlich anfänglicher Maus- oder Berührungsinteraktionen an der aktuellen Schiebereglerposition oder beim Verlassen eines Schiebereglers.

Problem:

Ab Anfang Juni 2016 unterscheiden sich verschiedene Browser hinsichtlich ihrer Reaktion auf die Verwendung von Bereichen / Schiebereglern. Fünf Szenarien sind relevant:

  1. anfängliches Mouse-Down (oder Touch-Start) an der aktuellen Schiebereglerposition
  2. anfängliches Mouse-Down (oder Touch-Start) an einer neuen Schiebereglerposition
  3. Jede nachfolgende Mausbewegung (oder Berührungsbewegung) nach 1 oder 2 entlang des Schiebereglers
  4. Jede nachfolgende Mausbewegung (oder Berührungsbewegung) nach 1 oder 2 nach einem Ende des Schiebereglers
  5. endgültiges Mouse-Up (oder Touch-End)

Die folgende Tabelle zeigt, wie sich mindestens drei verschiedene Desktop-Browser in Bezug auf das oben genannte Szenario in ihrem Verhalten unterscheiden:

Tabelle mit Browserunterschieden in Bezug darauf, auf welche Ereignisse sie wann reagieren

Lösung:

Die onRangeChangeFunktion bietet eine konsistente und vorhersehbare browserübergreifende Reaktion auf Interaktionen zwischen Bereich und Schieberegler. Es zwingt alle Browser, sich gemäß der folgenden Tabelle zu verhalten:

Tabelle mit dem Verhalten aller Browser, die die vorgeschlagene Lösung verwenden

In IE11 erlaubt der Code im Wesentlichen, dass alles gemäß dem Status Quo "change"funktioniert , dh, dass das Ereignis auf seine Standardweise funktioniert und das "input"Ereignis irrelevant ist, da es sowieso nie ausgelöst wird. In anderen Browsern wird das "change"Ereignis effektiv stummgeschaltet (um zu verhindern, dass zusätzliche und manchmal nicht leicht erkennbare Ereignisse ausgelöst werden). Darüber hinaus löst das "input"Ereignis seinen Listener nur aus, wenn sich der Wert des Bereichs / Schiebereglers ändert. Bei einigen Browsern (z. B. Firefox) tritt dies auf, weil der Listener in den Szenarien 1, 4 und 5 aus der obigen Liste effektiv stummgeschaltet ist.

(Wenn Sie wirklich benötigen, dass ein Listener in Szenario 1, 4 und / oder 5 aktiviert wird, können Sie versuchen, "mousedown"/ "touchstart", "mousemove"/ "touchmove"und / oder "mouseup"/ "touchend"Ereignisse einzubeziehen. Eine solche Lösung würde den Rahmen dieser Antwort sprengen.)

Funktionalität in mobilen Browsern:

Ich habe diesen Code in Desktop-Browsern getestet, aber nicht in mobilen Browsern. In einer anderen Antwort auf dieser Seite hat MBourne jedoch gezeigt, dass meine Lösung hier "... in jedem Browser zu funktionieren scheint, den ich finden konnte (Win-Desktop: IE, Chrome, Opera, FF; Android Chrome, Opera und FF, iOS Safari). " . (Danke MBourne.)

Verwendung:

Um diese Lösung zu verwenden, onRangeChangefügen Sie die Funktion aus der obigen Zusammenfassung (vereinfacht / minimiert) oder das Demo-Code-Snippet unten (funktional identisch, aber selbsterklärender) in Ihren eigenen Code ein. Rufen Sie es wie folgt auf:

onRangeChange(myRangeInputElmt, myListener);

Wo myRangeInputElmtist Ihr gewünschtes <input type="range">DOM-Element und welche myListenerListener / Handler-Funktion möchten Sie bei "change"ähnlichen Ereignissen aufrufen ?

Ihr Listener kann auf Wunsch parameterlos sein oder den eventParameter verwenden, dh je nach Ihren Anforderungen funktioniert eine der folgenden Möglichkeiten :

var myListener = function() {...

oder

var myListener = function(evt) {...

(Das Entfernen des Ereignis-Listeners aus dem inputElement (z. B. Verwenden von removeEventListener) wird in dieser Antwort nicht behandelt.)

Demo Beschreibung:

Im folgenden Codeausschnitt bietet die Funktion onRangeChangedie universelle Lösung. Der Rest des Codes ist lediglich ein Beispiel, um seine Verwendung zu demonstrieren. Jede Variable, die mit beginnt, my...ist für die universelle Lösung irrelevant und nur für die Demo vorhanden.

Die Demo zeigt den Bereich / Reglerwert sowie die Anzahl , wie oft den Standard "change", "input"und benutzerdefinierte "onRangeChange"Ereignisse ausgelöst hat (Zeilen A, B und C jeweils). Beachten Sie beim Ausführen dieses Snippets in verschiedenen Browsern Folgendes, wenn Sie mit dem Bereich / Schieberegler interagieren:

  • In IE11 ändern sich die Werte in den Zeilen A und C in den obigen Szenarien 2 und 3, während sich die Zeile B nie ändert.
  • In Chrome und Safari ändern sich die Werte in den Zeilen B und C in Szenario 2 und 3, während sich Zeile A nur für Szenario 5 ändert.
  • In Firefox ändert sich der Wert in Zeile A nur für Szenario 5, Zeile B für alle fünf Szenarien und Zeile C nur für Szenario 2 und 3.
  • In allen oben genannten Browsern sind die Änderungen in Zeile C (der vorgeschlagenen Lösung) identisch, dh nur für die Szenarien 2 und 3.

Demo-Code:

// 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>

Anerkennung:

Während die Implementierung hier größtenteils meine eigene ist, wurde sie von MBournes Antwort inspiriert . Diese andere Antwort deutete darauf hin, dass die Ereignisse "Eingabe" und "Änderung" zusammengeführt werden könnten und der resultierende Code sowohl in Desktop- als auch in mobilen Browsern funktionieren würde. Der Code in dieser Antwort führt jedoch dazu, dass versteckte "zusätzliche" Ereignisse ausgelöst werden, was an und für sich problematisch ist, und die ausgelösten Ereignisse unterscheiden sich zwischen den Browsern, ein weiteres Problem. Meine Implementierung hier löst diese Probleme.

Schlüsselwörter:

Die Schiebereglerereignisse für den JavaScript-Eingabetyp ändern die Kompatibilität des Eingabebrowsers für browserübergreifende Desktop-Mobilgeräte ohne jQuery

Andrew Willems
quelle
31

Ich poste dies als Antwort, weil es verdient, eine eigene Antwort zu sein, anstatt einen Kommentar unter einer weniger nützlichen Antwort. Ich finde diese Methode viel besser als die akzeptierte Antwort, da sie alle js in einer vom HTML getrennten Datei speichern kann.

Antwort von Jamrelian in seinem Kommentar unter der akzeptierten Antwort.

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

Beachten Sie jedoch diesen Kommentar von Jaime

Beachten Sie nur, dass Sie mit dieser Lösung in Chrome zwei Anrufe an den Handler erhalten (einen pro Ereignis). Wenn Sie sich darum kümmern, müssen Sie sich dagegen schützen.

Wie in wird das Ereignis ausgelöst, wenn Sie die Maus nicht mehr bewegen, und dann erneut, wenn Sie die Maustaste loslassen.

Aktualisieren

In Bezug auf das changeEreignis und das inputEreignis, bei denen die Funktionalität zweimal ausgelöst wird, ist dies so gut wie kein Problem.

Wenn eine Funktion aktiviert ist input, ist es äußerst unwahrscheinlich, dass beim Auslösen des changeEreignisses ein Problem auftritt.

inputWird schnell ausgelöst, wenn Sie einen Schieberegler für die Bereichseingabe ziehen. Die Sorge um einen weiteren Funktionsaufruf, der am Ende ausgelöst wird, ist wie die Sorge um einen einzelnen Wassertropfen im Vergleich zum Ozean des Wassers, der die inputEreignisse darstellt.

Der Grund für die Aufnahme des changeEreignisses ist aus Gründen der Browserkompatibilität (hauptsächlich mit dem Internet Explorer).

Daniel Tonon
quelle
plus 1 Für die einzige Lösung, die mit dem Internet Explorer funktioniert !! Haben Sie es auch in anderen Browsern getestet?
Jessica
1
Es ist jQuery, daher sind die Chancen, dass es nicht funktioniert, ziemlich gering. Ich habe selbst nicht gesehen, dass es nirgendwo funktioniert. Es funktioniert sogar auf Mobilgeräten, was großartig ist.
Daniel Tonon
30

UPDATE: Ich lasse diese Antwort hier als Beispiel für die Verwendung von Mausereignissen zur Verwendung von Bereichs- / Schieberegler-Interaktionen in Desktop-Browsern (aber nicht in mobilen Browsern). Jetzt habe ich jedoch auch eine völlig andere und meiner Meinung nach bessere Antwort an anderer Stelle auf dieser Seite geschrieben, die einen anderen Ansatz verwendet, um eine browserübergreifende Desktop- und Mobillösung für dieses Problem bereitzustellen .

Ursprüngliche Antwort:

Zusammenfassung: Eine browserübergreifende, einfache JavaScript-Lösung (dh no-jQuery), mit der Eingabewerte für den Lesebereich ohne Verwendung on('input'...und / oder on('change'...inkonsistent zwischen Browsern verwendet werden können.

Ab heute (Ende Februar 2016) gibt es immer noch Browser-Inkonsistenzen, daher biete ich hier eine neue Problemumgehung an.

Das Problem: Bei Verwendung einer Bereichseingabe, dh eines Schiebereglers, on('input'...werden kontinuierlich aktualisierte Bereichswerte in Mac und Windows Firefox, Chrome und Opera sowie Mac Safari bereitgestellt, während on('change'...der Bereichswert nur beim Hochfahren mit der Maus gemeldet wird. Im Gegensatz dazu funktioniert Internet Explorer (v11) on('input'...überhaupt nicht und on('change'...wird kontinuierlich aktualisiert.

Ich berichte hier über 2 Strategien, um in allen Browsern mit Vanilla JavaScript (dh ohne jQuery) identische kontinuierliche Wertewerte zu erhalten, indem die Ereignisse mousedown, mousemove und (möglicherweise) mouseup verwendet werden.

Strategie 1: Kürzer aber weniger effizient

Wenn Sie kürzeren Code gegenüber effizienterem Code bevorzugen, können Sie diese erste Lösung verwenden, die mousesdown und mousemove verwendet, jedoch nicht mouseup. Dadurch wird der Schieberegler nach Bedarf gelesen, aber während eines Mouseover-Ereignisses wird unnötig weiter ausgelöst, selbst wenn der Benutzer nicht geklickt hat und daher den Schieberegler nicht zieht. Es liest im Wesentlichen den Bereichswert sowohl nach "Mousedown" als auch während "Mousemove" -Ereignissen und verzögert jede Verwendung geringfügig 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"/>

Strategie 2: Länger aber effizienter

Wenn Sie effizienteren Code benötigen und eine längere Codelänge tolerieren können, können Sie die folgende Lösung verwenden, die Mousedown, Mousemove und Mouseup verwendet. Dadurch wird auch der Schieberegler nach Bedarf gelesen, der Lesevorgang wird jedoch entsprechend beendet, sobald die Maustaste losgelassen wird. Der wesentliche Unterschied besteht darin, dass erst nach "Mousedown" auf "Mousemove" gewartet wird und nach "Mouseup" nicht mehr auf "Mousemove" gewartet wird.

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: Ausführlichere Erklärung der Notwendigkeit und Implementierung der oben genannten Problemumgehungen

Der folgende Code veranschaulicht zahlreiche Aspekte dieser Strategie ausführlicher. Erklärungen sind in die Demonstration eingebettet:

Andrew Willems
quelle
Wirklich gute Infos. Vielleicht sogar irgendwann Kontaktereignisse hinzufügen? touchmovesollte ausreichen, und ich denke, es kann genauso behandelt werden wie mousemovein diesem Fall.
Nick
@nick, siehe meine andere Antwort auf dieser Seite für eine andere Lösung, die mobile Browserfunktionen bietet.
Andrew Willems
Dies ist keine zugängliche Antwort, da die Tastaturnavigation die Ereignisse nicht
auslöst,
@LocalPCGuy, du hast recht. Meine Antwort hat die integrierte Tastatursteuerbarkeit der Schieberegler beeinträchtigt. Ich habe den Code aktualisiert, um die Tastatursteuerung wiederherzustellen. Wenn Sie andere Möglichkeiten kennen, wie mein Code die integrierte Barrierefreiheit beeinträchtigt, lassen Sie es mich wissen.
Andrew Willems
7

Die Lösungen von Andrew Willem sind nicht mit Mobilgeräten kompatibel.

Hier ist eine Modifikation seiner zweiten Lösung, die in Edge, IE, Opera, FF, Chrome, iOS Safari und mobilen Entsprechungen funktioniert (die ich testen konnte):

Update 1: Der Teil "requestAnimationFrame" wurde entfernt, da ich damit einverstanden bin, dass dies nicht erforderlich ist:

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

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

Update 2: Antwort auf Andrews aktualisierte Antwort vom 2. Juni 2016:

Danke, Andrew - das scheint in jedem Browser zu funktionieren, den ich finden konnte (Win Desktop: IE, Chrome, Opera, FF; Android Chrome, Opera und FF, iOS Safari).

Update 3: if-Lösung ("oninput in slider")

Das Folgende scheint in allen oben genannten Browsern zu funktionieren. (Ich kann die Originalquelle jetzt nicht finden.) Ich habe diese verwendet, aber sie ist anschließend im IE fehlgeschlagen, und so habe ich nach einer anderen gesucht, daher bin ich hier gelandet.

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

Aber bevor ich Ihre Lösung überprüfte, bemerkte ich, dass dies im IE wieder funktionierte - vielleicht gab es einen anderen Konflikt.

MBourne
quelle
1
Ich habe bestätigt, dass Ihre Lösung in zwei verschiedenen Desktop-Browsern funktioniert: Mac Firefox und Windows IE11. Ich persönlich konnte keine mobilen Möglichkeiten prüfen. Aber das sieht nach einem großartigen Update meiner Antwort aus, das es auf mobile Geräte erweitert. (Ich gehe davon aus, dass "Eingabe" und "Änderung" sowohl Mauseingaben auf einem Desktop-System als auch Berührungseingaben auf einem mobilen System akzeptieren.) Sehr gute Arbeit, die allgemein anwendbar sein sollte und es wert ist, erkannt und implementiert zu werden!
Andrew Willems
1
Nachdem ich weiter gegraben habe, habe ich festgestellt, dass Ihre Lösung mehrere Nachteile hat. Erstens halte ich den requestAnimationFrame für unnötig. Zweitens löst der Code zusätzliche Ereignisse aus (mindestens 3 Ereignisse, z. B. Mouseup, in einigen Browsern). Drittens unterscheiden sich die genauen Ereignisse zwischen einigen Browsern. Ich habe daher eine separate Antwort gegeben, die auf Ihrer ursprünglichen Idee basiert, eine Kombination aus "Eingabe" - und "Änderungs" -Ereignissen zu verwenden, um Ihnen die ursprüngliche Inspiration zu würdigen.
Andrew Willems
2

Für ein gutes browserübergreifendes Verhalten und verständlichen Code verwenden Sie am besten das onchange-Attribut in Kombination mit einem Formular:

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>

Das gleiche mit Eingang wird der Wert direkt geändert.

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
quelle
0

Noch ein anderer Ansatz - setzen Sie einfach ein Flag auf ein Element, das signalisiert, welche Art von Ereignis behandelt werden soll:

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
quelle
-3

Sie können das JavaScript-Ereignis "ondrag" verwenden, um kontinuierlich zu feuern. Es ist aus folgenden Gründen besser als "Eingabe":

  1. Browser-Unterstützung.

  2. Könnte zwischen "ondrag" und "change" Ereignis unterscheiden. "Eingabe" wird sowohl zum Ziehen als auch zum Ändern ausgelöst.

In jQuery:

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

});

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

Scheich
quelle