evento onchange su input type = range non si attiva in Firefox durante il trascinamento


252

Quando ho giocato con <input type="range"> , Firefox attiva un evento di scambio solo se rilasciamo il dispositivo di scorrimento in una nuova posizione in cui Chrome e altri generano eventi di scambio mentre il dispositivo di scorrimento viene trascinato.

Come posso farlo accadere trascinando in 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)">


4
Se l'elemento intervallo ha lo stato attivo, è possibile spostare il dispositivo di scorrimento utilizzando i tasti freccia. E in quel caso, inoltre, il onchangenon spara. È stato nella risoluzione di questo problema che ho trovato questa domanda.
Sildoreth,

Risposte:


452

Apparentemente Chrome e Safari sono sbagliati: onchangedovrebbero essere attivati ​​solo quando l'utente rilascia il mouse. Per ottenere aggiornamenti continui, è necessario utilizzare l' oninputevento, che acquisirà aggiornamenti in tempo reale su Firefox, Safari e Chrome, sia dal mouse che dalla tastiera.

Tuttavia, oninputin IE10 non è supportato, quindi la soluzione migliore è combinare i due gestori di eventi, in questo modo:

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

Dai un'occhiata a questo thread Bugzilla per ulteriori informazioni.


113
O $("#myelement").on("input change", function() { doSomething(); });con jQuery.
Alex Vang,

19
Basta notare che con questa soluzione, in Chrome riceverai due chiamate al gestore (una per evento), quindi se ti preoccupi, allora devi proteggerti.
Jaime

3
Avevo problemi con l'evento "change" che non si attivava in Chrome mobile (v34), ma l'aggiunta di "input" nell'equazione lo ha risolto per me, senza avere effetti negativi altrove.
brins0

7
@Jaime: " se ti interessa, allora devi proteggerti. " Come posso farlo, per favore?

2
Purtroppo, onchange()non funziona su web mobile come Android Chromee iOS Safari. Qualche suggerimento alternativo?
Alston,

41

SOMMARIO:

Fornisco qui una capacità desktop-e-mobile no-jQuery cross-browser per rispondere in modo coerente alle interazioni intervallo / dispositivo di scorrimento, cosa impossibile nei browser attuali. In pratica costringe tutti i browser a emulare l' on("change"...evento IE11 per i loro eventi on("change"...o on("input".... La nuova funzione è ...

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

... dov'è rl'elemento di input del range ed fè il tuo ascoltatore. L'ascoltatore verrà chiamato dopo qualsiasi interazione che modifica il valore dell'intervallo / dispositivo di scorrimento, ma non dopo interazioni che non modificano quel valore, comprese le interazioni iniziali del mouse o del tocco nella posizione corrente del dispositivo di scorrimento o quando si sposta da una delle estremità del dispositivo di scorrimento.

Problema:

All'inizio di giugno 2016, diversi browser si differenziano per il modo in cui rispondono all'utilizzo del range / cursore. Sono rilevanti cinque scenari:

  1. mouse iniziale in basso (o touch-start) nella posizione corrente del cursore
  2. mouse-down iniziale (o touch-start) in una nuova posizione del cursore
  3. qualsiasi successivo movimento del mouse (o tocco) dopo 1 o 2 lungo il cursore
  4. qualsiasi movimento successivo del mouse (o tocco) dopo 1 o 2 oltre una delle estremità del cursore
  5. mouse-up finale (o touch-end)

La tabella seguente mostra come almeno tre diversi browser desktop differiscono nel loro comportamento rispetto a quale dei suddetti scenari rispondono:

tabella che mostra le differenze del browser rispetto a quali eventi rispondono e quando

Soluzione:

La onRangeChangefunzione fornisce una risposta cross-browser coerente e prevedibile alle interazioni intervallo / cursore. Forza tutti i browser a comportarsi secondo la seguente tabella:

tabella che mostra il comportamento di tutti i browser utilizzando la soluzione proposta

In IE11, il codice consente essenzialmente a tutto di funzionare secondo lo status quo, ovvero consente "change"all'evento di funzionare nel suo modo standard e l' "input"evento è irrilevante in quanto non si attiva comunque. In altri browser, l' "change"evento viene efficacemente messo a tacere (per evitare che vengano attivati ​​eventi extra e talvolta non apparentemente evidenti). Inoltre, il"input" evento attiva il suo ascoltatore solo quando cambia il valore dell'intervallo / cursore. Per alcuni browser (ad esempio Firefox) ciò si verifica perché l'ascoltatore è effettivamente messo a tacere negli scenari 1, 4 e 5 dall'elenco precedente.

(Se si richiede veramente l'attivazione di un ascoltatore in uno degli scenari 1, 4 e / o 5, è possibile provare a incorporare "mousedown"/ "touchstart", "mousemove"/ "touchmove"e / o "mouseup"/ "touchend"eventi. Tale soluzione va oltre lo scopo di questa risposta.)

Funzionalità nei browser mobili:

Ho testato questo codice nei browser desktop ma non in tutti i browser mobili. Tuttavia, in un'altra risposta su questa pagina MBourne ha dimostrato che la mia soluzione qui "... sembra funzionare in tutti i browser che ho trovato (Win desktop: IE, Chrome, Opera, FF; Android Chrome, Opera e FF, iOS Safari) " . (Grazie MBourne.)

Uso:

Per utilizzare questa soluzione, includere la onRangeChangefunzione dal riepilogo sopra (semplificato / minimizzato) o lo snippet di codice demo di seguito (funzionalmente identico ma più autoesplicativo) nel proprio codice. Invocalo come segue:

onRangeChange(myRangeInputElmt, myListener);

dove si myRangeInputElmttrova l' <input type="range">elemento DOM desiderato ed myListenerè la funzione listener / handler su cui si desidera richiamare "change"eventi simili.

Il tuo listener può essere privo di parametri se lo desideri o può utilizzare il eventparametro, ovvero una delle seguenti funzioni funzionerebbe, a seconda delle tue esigenze:

var myListener = function() {...

o

var myListener = function(evt) {...

(La rimozione del listener di eventi inputdall'elemento (ad es. Utilizzo removeEventListener) non viene risolta in questa risposta.)

Descrizione della demo:

Nel frammento di codice seguente, la funzione onRangeChangefornisce la soluzione universale. Il resto del codice è semplicemente un esempio per dimostrarne l'utilizzo. Qualsiasi variabile che inizia con my...è irrilevante per la soluzione universale ed è presente solo per il bene della demo.

Gli spettacoli demo valore gamma / cursore così come il numero di volte lo standard "change", "input"e personalizzate "onRangeChange"eventi sono alimentate (righe A, B e C, rispettivamente). Quando esegui questo frammento in diversi browser, tieni presente quanto segue mentre interagisci con l'intervallo / dispositivo di scorrimento:

  • In IE11, i valori nelle righe A e C cambiano entrambi negli scenari 2 e 3 sopra mentre la riga B non cambia mai.
  • In Chrome e Safari, i valori nelle righe B e C cambiano entrambi negli scenari 2 e 3 mentre la riga A cambia solo per lo scenario 5.
  • In Firefox, il valore nella riga A cambia solo per lo scenario 5, la riga B cambia per tutti e cinque gli scenari e la riga C cambia solo per gli scenari 2 e 3.
  • In tutti i browser di cui sopra, le modifiche nella riga C (la soluzione proposta) sono identiche, vale a dire solo per gli scenari 2 e 3.

Codice demo:

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

Credito:

Mentre l'implementazione qui è in gran parte la mia, è stata ispirata dalla risposta di MBourne . L'altra risposta suggeriva che gli eventi "input" e "change" potevano essere uniti e che il codice risultante avrebbe funzionato sia nei browser desktop che mobili. Tuttavia, il codice in quella risposta comporta la generazione di eventi "extra" nascosti, il che è di per sé problematico e gli eventi generati differiscono tra i browser, un ulteriore problema. La mia implementazione qui risolve questi problemi.

parole chiave:

Gli eventi del dispositivo di scorrimento dell'intervallo del tipo di input JavaScript cambiano la compatibilità del browser di input cross-browser desktop mobile no-jQuery


31

Sto postando questo come una risposta perché merita di essere la propria risposta piuttosto che un commento sotto una risposta meno utile. Trovo questo metodo molto migliore della risposta accettata poiché può tenere tutti i js in un file separato dall'HTML.

Risposta fornita da Jamrelian nel suo commento sotto la risposta accettata.

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

Basta essere consapevoli di questo commento di Jaime però

Basta notare che con questa soluzione, in Chrome riceverai due chiamate al gestore (una per evento), quindi se ti interessa, allora devi proteggerti.

Come in esso verrà generato l'evento quando hai smesso di spostare il mouse, e poi di nuovo quando rilasci il pulsante del mouse.

Aggiornare

In relazione changeall'evento einput all'evento che causano l'attivazione della funzionalità due volte, questo è praticamente un problema.

Se si attiva una funzione input, è estremamente improbabile che si verifichi un problema quando si attiva l' changeevento.

inputsi attiva rapidamente quando si trascina un cursore di input dell'intervallo. Preoccuparsi di un'altra chiamata di funzione che spara alla fine è come preoccuparsi di una singola goccia d'acqua rispetto all'oceano che è ilinput eventi.

Il motivo per cui si include anche l' changeevento è per motivi di compatibilità del browser (principalmente con IE).


plus 1 Per l'unica soluzione che funziona con Internet Explorer !! L'hai provato anche su altri browser?
Jessica,

1
È jQuery, quindi le probabilità che non funzioni sono piuttosto basse. Non l'ho visto funzionare da nessuna parte da solo. Funziona anche su dispositivi mobili, il che è fantastico.
Daniel Tonon,

30

AGGIORNAMENTO: Lascio qui questa risposta come esempio di come utilizzare gli eventi del mouse per utilizzare le interazioni di intervallo / cursore nei browser desktop (ma non mobili). Tuttavia, ora ho anche scritto una risposta completamente diversa e, credo, migliore altrove in questa pagina che utilizza un approccio diverso per fornire una soluzione desktop e mobile cross-browser a questo problema.

Risposta originale:

Riepilogo: una soluzione JavaScript (cioè no-jQuery) tra browser, che consente di leggere i valori di input dell'intervallo senza utilizzare on('input'...e / o on('change'...che funzionano in modo incoerente tra i browser.

Ad oggi (fine febbraio 2016), c'è ancora un'incoerenza del browser, quindi sto fornendo una nuova soluzione qui.

Il problema: quando si utilizza un input di intervallo, ovvero un dispositivo di scorrimento, on('input'...fornisce valori di intervallo costantemente aggiornati in Mac e Windows Firefox, Chrome e Opera nonché Mac Safari, mentre on('change'...riporta il valore dell'intervallo solo al passaggio del mouse. Al contrario, in Internet Explorer (v11), on('input'...non funziona affatto e on('change'...viene continuamente aggiornato.

Riporto qui 2 strategie per ottenere un identico rapporto di valori di intervallo continuo in tutti i browser utilizzando JavaScript vanilla (ovvero senza jQuery) utilizzando gli eventi mousedown, mousemove e (possibilmente) del mouse.

Strategia 1: più breve ma meno efficiente

Se si preferisce un codice più breve rispetto a un codice più efficiente, è possibile utilizzare questa prima soluzione che utilizza mousesdown e mousemove ma non il mouseup. Questo legge il dispositivo di scorrimento quando necessario, ma continua a sparare inutilmente durante qualsiasi evento di passaggio del mouse, anche quando l'utente non ha fatto clic e non trascina quindi il dispositivo di scorrimento. Legge essenzialmente il valore dell'intervallo sia dopo 'mousedown' che durante gli eventi 'mousemove', ritardando leggermente ogni utilizzo 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: più lunga ma più efficiente

Se hai bisogno di un codice più efficiente e puoi tollerare una lunghezza del codice più lunga, puoi utilizzare la seguente soluzione che utilizza mousedown, mousemove e mouseup. Questo legge anche il cursore, se necessario, ma interrompe appropriatamente la lettura non appena viene rilasciato il pulsante del mouse. La differenza essenziale è che inizia ad ascoltare 'mousemove' solo dopo 'mousedown', e smette di ascoltare 'mousemove' dopo '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: spiegazione più completa della necessità e dell'attuazione delle soluzioni di cui sopra

Il seguente codice mostra in modo più completo numerosi aspetti di questa strategia. Le spiegazioni sono integrate nella dimostrazione:


Informazioni davvero buone. Forse potresti persino aggiungere eventi touch in qualche momento? touchmovedovrebbe essere sufficiente e penso che possa essere trattato proprio come mousemovein questo caso.
nick

@nick, per favore vedi la mia altra risposta in questa pagina per una soluzione diversa che fornisce funzionalità di browser mobile.
Andrew Willems,

Questa non è una risposta accessibile, poiché la navigazione da tastiera non genererà eventi poiché sono tutti incentrati sul mouse
LocalPCGuy

@LocalPCGuy, hai ragione. La mia risposta ha rotto la controllabilità da tastiera integrata degli slider. Ho aggiornato il codice per ripristinare il controllo da tastiera. Se conosci altri modi in cui il mio codice rompe l'accessibilità integrata, fammi sapere.
Andrew Willems,

7

Le soluzioni di Andrew Willem non sono compatibili con i dispositivi mobili.

Ecco una modifica della sua seconda soluzione che funziona in Edge, IE, Opera, FF, Chrome, iOS Safari e dispositivi mobili equivalenti (che ho potuto testare):

Aggiornamento 1: Rimossa la porzione "requestAnimationFrame", poiché concordo che non è necessario:

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

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

Aggiornamento 2: Risposta alla risposta aggiornata di Andrew del 2 giugno 2016:

Grazie Andrew - sembra funzionare in tutti i browser che ho trovato (Win desktop: IE, Chrome, Opera, FF; Android Chrome, Opera e FF, iOS Safari).

Aggiornamento 3: soluzione if ("oninput in slider)

Quanto segue sembra funzionare su tutti i browser sopra indicati. (Non riesco a trovare la fonte originale ora.) Stavo usando questo, ma successivamente non è riuscito su IE e quindi sono andato a cercarne uno diverso, quindi sono finito qui.

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

Ma prima di controllare la tua soluzione, ho notato che funzionava di nuovo in IE - forse c'era qualche altro conflitto.


1
Ho confermato che la tua soluzione funziona in due diversi browser desktop: Mac Firefox e Windows IE11. Non sono stato personalmente in grado di verificare alcuna possibilità mobile. Ma questo sembra un ottimo aggiornamento sulla mia risposta che lo amplia per includere i dispositivi mobili. (Suppongo che "input" e "change" accettino sia l'input del mouse su un sistema desktop sia l'input touch su un sistema mobile.) Lavoro molto bello che dovrebbe essere ampiamente applicabile e vale la pena ottenere riconoscimento e implementazione!
Andrew Willems,

1
Dopo aver scavato ulteriormente mi sono reso conto che la tua soluzione presenta diversi inconvenienti. Innanzitutto, credo che requestAnimationFrame non sia necessario. In secondo luogo, il codice genera eventi extra (almeno 3 eventi su, ad esempio, mouseup, in alcuni browser). In terzo luogo, gli eventi esatti generati differiscono tra alcuni browser. Ho quindi fornito una risposta separata basata sulla tua idea iniziale di utilizzare una combinazione di eventi "input" e "change", dandoti credito per l'ispirazione originale.
Andrew Willems,

2

Per un buon comportamento tra browser e un codice comprensibile, la cosa migliore è usare l'attributo onchange in combinazione con un modulo:

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>

Lo stesso usando oninput , il valore viene modificato direttamente.

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>


0

Ancora un altro approccio: basta impostare una bandiera su un elemento che segnala quale tipo di evento dovrebbe essere gestito:

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

-3

È possibile utilizzare l'evento JavaScript "ondrag" per generare continuamente. È meglio di "input" per i seguenti motivi:

  1. Supporto per il browser.

  2. Potrebbe distinguere tra "ondrag" e l'evento "change". "input" viene attivato sia per trascinare che per cambiare.

In jQuery:

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

});

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.