El evento onchange en input type = range no se dispara en firefox mientras se arrastra

252

Cuando jugué con <input type="range">, Firefox activa un evento de cambio solo si colocamos el control deslizante en una nueva posición donde Chrome y otros activan eventos de cambio mientras se arrastra el control deslizante.

¿Cómo puedo hacer que suceda arrastrando en 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
fuente
44
Si el elemento de rango tiene foco, puede mover el control deslizante usando las teclas de flecha. Y en ese caso, también, el onchangeno dispara. Fue en la solución de problemas que encontré esta pregunta.
Sildoreth

Respuestas:

452

Aparentemente, Chrome y Safari están equivocados: onchangesolo deberían activarse cuando el usuario suelta el mouse. Para obtener actualizaciones continuas, debe usar el oninputevento, que capturará actualizaciones en vivo en Firefox, Safari y Chrome, tanto desde el mouse como desde el teclado.

Sin embargo, oninputno es compatible con IE10, por lo que su mejor opción es combinar los dos controladores de eventos, de esta manera:

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

Mira este hilo de Bugzilla para más información.

Frederik
fuente
113
O $("#myelement").on("input change", function() { doSomething(); });con jQuery.
Alex Vang
19
Solo tenga en cuenta que con esta solución, en Chrome obtendrá dos llamadas al controlador (una por evento), por lo que si se preocupa por eso, debe evitarlo.
Jaime
3
Estaba teniendo problemas con el evento 'cambio' que no se activaba en el dispositivo móvil Chrome (v34), pero agregar 'entrada' a la ecuación me lo solucionó, sin tener efectos perjudiciales en otros lugares.
brins0
77
@Jaime: " si te importa eso, entonces debes protegerte " . ¿Cómo puedo hacerlo, por favor?
2
Lamentablemente, onchange()no funciona en la web móvil como Android Chromey iOS Safari. ¿Alguna sugerencia alternativa?
Alston
41

RESUMEN:

Proporciono aquí una capacidad de escritorio y móvil de navegador cruzado sin jQuery para responder consistentemente a las interacciones de rango / control deslizante, algo que no es posible en los navegadores actuales. Básicamente obliga a todos los navegadores a emular el on("change"...evento de IE11 para sus eventos on("change"...o para ellos on("input".... La nueva función es ...

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

... donde restá su elemento de entrada de rango y fes su oyente. Se llamará al oyente después de cualquier interacción que cambie el valor del rango / control deslizante, pero no después de las interacciones que no cambian ese valor, incluidas las interacciones iniciales con el mouse o el toque en la posición actual del control deslizante o al salir de cualquier extremo del control deslizante.

Problema:

A principios de junio de 2016, los diferentes navegadores difieren en términos de cómo responden al uso del rango / control deslizante. Cinco escenarios son relevantes:

  1. mouse-down inicial (o touch-start) en la posición actual del control deslizante
  2. mouse-down inicial (o touch-start) en una nueva posición del control deslizante
  3. cualquier movimiento posterior del mouse (o toque) después de 1 o 2 a lo largo del control deslizante
  4. cualquier movimiento posterior del mouse (o toque) después de 1 o 2 más allá de cualquier extremo del control deslizante
  5. mouse-up final (o touch-end)

La siguiente tabla muestra cómo al menos tres navegadores de escritorio diferentes difieren en su comportamiento con respecto a cuál de los escenarios anteriores responden:

tabla que muestra las diferencias del navegador con respecto a qué eventos responden y cuándo

Solución:

La onRangeChangefunción proporciona una respuesta coherente y predecible entre navegadores a las interacciones de rango / control deslizante. Obliga a todos los navegadores a comportarse de acuerdo con la siguiente tabla:

tabla que muestra el comportamiento de todos los navegadores que utilizan la solución propuesta

En IE11, el código esencialmente permite que todo funcione según el status quo, es decir, permite que el "change"evento funcione de la manera estándar y el "input"evento es irrelevante ya que nunca se dispara de todos modos. En otros navegadores, el "change"evento se silencia efectivamente (para evitar que se disparen eventos adicionales y, a veces, no aparentes). Además, el "input"evento activa su escucha solo cuando cambia el valor del rango / control deslizante. Para algunos navegadores (por ejemplo, Firefox) esto ocurre porque el oyente está silenciado efectivamente en los escenarios 1, 4 y 5 de la lista anterior.

(Si realmente necesita que se active un oyente en los escenarios 1, 4 y / o 5, puede intentar incorporar "mousedown"/ "touchstart", "mousemove"/ "touchmove"y / o "mouseup"/ "touchend"eventos. Tal solución está más allá del alcance de esta respuesta).

Funcionalidad en navegadores móviles:

He probado este código en navegadores de escritorio pero no en ningún navegador móvil. Sin embargo, en otra respuesta en esta página, MBourne ha demostrado que mi solución aquí "... parece funcionar en todos los navegadores que pude encontrar (escritorio de Win: IE, Chrome, Opera, FF; Android Chrome, Opera y FF, iOS Safari) " . (Gracias MBourne)

Uso:

Para usar esta solución, incluya la onRangeChangefunción del resumen anterior (simplificado / minimizado) o el fragmento de código de demostración a continuación (funcionalmente idéntico pero más explicativo) en su propio código. Invocarlo de la siguiente manera:

onRangeChange(myRangeInputElmt, myListener);

donde myRangeInputElmtestá el <input type="range">elemento DOM deseado y myListeneres la función de escucha / controlador que desea invocar en "change"eventos similares.

Su oyente puede no tener parámetros si lo desea o puede usar el eventparámetro, es decir, cualquiera de los siguientes funcionaría, dependiendo de sus necesidades:

var myListener = function() {...

o

var myListener = function(evt) {...

(La eliminación del detector de eventos del inputelemento (p. Ej., El uso removeEventListener) no se aborda en esta respuesta).

Descripción de demostración:

En el fragmento de código a continuación, la función onRangeChangeproporciona la solución universal. El resto del código es simplemente un ejemplo para demostrar su uso. Cualquier variable que comience my...es irrelevante para la solución universal y solo está presente por el bien de la demostración.

Los espectáculos de demostración el valor de rango / deslizante, así como el número de veces que el estándar "change", "input"y la costumbre "onRangeChange"eventos han disparado (filas A, B y C respectivamente). Al ejecutar este fragmento en diferentes navegadores, tenga en cuenta lo siguiente mientras interactúa con el rango / control deslizante:

  • En IE11, los valores en las filas A y C cambian en los escenarios 2 y 3 anteriores, mientras que la fila B nunca cambia.
  • En Chrome y Safari, los valores en las filas B y C cambian en los escenarios 2 y 3, mientras que la fila A cambia solo para el escenario 5.
  • En Firefox, el valor en la fila A cambia solo para el escenario 5, la fila B cambia para los cinco escenarios y la fila C cambia solo para los escenarios 2 y 3.
  • En todos los navegadores anteriores, los cambios en la fila C (la solución propuesta) son idénticos, es decir, solo para los escenarios 2 y 3.

Código de demostración:

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

Crédito:

Si bien la implementación aquí es en gran medida mía, se inspiró en la respuesta de MBourne . Esa otra respuesta sugirió que los eventos de "entrada" y "cambio" podrían fusionarse y que el código resultante funcionaría en los navegadores de escritorio y móviles. Sin embargo, el código en esa respuesta da como resultado eventos "extra" ocultos que se disparan, lo que en sí mismo es problemático, y los eventos disparados difieren entre los navegadores, un problema adicional. Mi implementación aquí resuelve esos problemas.

Palabras clave:

Los eventos del control deslizante de rango de tipo de entrada de JavaScript cambian la compatibilidad del navegador de entrada entre navegadores de escritorio móvil no-jQuery

Andrew Willems
fuente
31

Estoy publicando esto como una respuesta porque merece ser su propia respuesta en lugar de un comentario en una respuesta menos útil. Este método me parece mucho mejor que la respuesta aceptada, ya que puede mantener todos los js en un archivo separado del HTML.

Respuesta proporcionada por Jamrelian en su comentario bajo la respuesta aceptada.

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

Solo ten en cuenta este comentario de Jaime

Solo tenga en cuenta que con esta solución, en Chrome obtendrá dos llamadas al controlador (una por evento), por lo que si se preocupa por eso, debe evitarlo.

Como en él, se activará el evento cuando haya dejado de mover el mouse, y luego nuevamente cuando suelte el botón del mouse.

Actualizar

En relación con el changeevento y el inputevento que hace que la funcionalidad se active dos veces, esto no es un problema.

Si tiene una función activada input, es extremadamente improbable que haya un problema cuando changese active el evento.

inputse dispara rápidamente al arrastrar un control deslizante de entrada de rango. Preocuparse por una llamada de función más al final es como preocuparse por una sola gota de agua en comparación con el océano de agua que son los inputeventos.

La razón para incluso incluir el changeevento es en aras de la compatibilidad del navegador (principalmente con IE).

Daniel Tonon
fuente
plus 1 ¡Para la única solución que funciona con Internet Explorer! ¿Lo probaste también en otros navegadores?
Jessica
1
Es jQuery, por lo que las posibilidades de que no funcione son bastante bajas. No he visto que no funcione en ningún lado. Incluso funciona en dispositivos móviles, lo cual es genial.
Daniel Tonon
30

ACTUALIZACIÓN: Dejo esta respuesta aquí como un ejemplo de cómo usar eventos del mouse para usar interacciones de rango / control deslizante en navegadores de escritorio (pero no móviles). Sin embargo, ahora también he escrito una respuesta completamente diferente y, creo, mejor en otra parte de esta página que utiliza un enfoque diferente para proporcionar una solución de escritorio y móvil entre navegadores para este problema.

Respuesta original:

Resumen: una solución de JavaScript simple para todos los navegadores (es decir, no jQuery) para permitir la lectura de valores de entrada de rango sin usar on('input'...y / o on('change'...que funcionan de manera inconsistente entre navegadores.

A partir de hoy (finales de febrero de 2016), todavía hay inconsistencia en el navegador, por lo que estoy proporcionando una nueva solución aquí.

El problema: cuando se usa una entrada de rango, es decir, un control deslizante, on('input'...proporciona valores de rango actualizados continuamente en Mac y Windows Firefox, Chrome y Opera, así como Mac Safari, mientras que on('change'...solo informa el valor del rango al mover el mouse. Por el contrario, en Internet Explorer (v11), on('input'...no funciona en absoluto y on('change'...se actualiza continuamente.

Presento aquí 2 estrategias para obtener informes de valores de rango continuo idénticos en todos los navegadores que usan JavaScript de vainilla (es decir, no jQuery) usando los eventos mousedown, mousemove y (posiblemente) mouseup.

Estrategia 1: más corta pero menos eficiente

Si prefiere un código más corto sobre un código más eficiente, puede usar esta primera solución que usa mouse y mousemove pero no mouseup. Esto lee el control deslizante según sea necesario, pero continúa disparando innecesariamente durante cualquier evento de desplazamiento del mouse, incluso cuando el usuario no ha hecho clic y, por lo tanto, no está arrastrando el control deslizante. Básicamente, lee el valor del rango después de 'mousedown' y durante los eventos 'mousemove', retrasando ligeramente cada uso 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"/>

Estrategia 2: más larga pero más eficiente

Si necesita un código más eficiente y puede tolerar una longitud de código más larga, puede usar la siguiente solución que usa mousedown, mousemove y mouseup. Esto también lee el control deslizante según sea necesario, pero deja de leerlo tan pronto como se suelta el botón del mouse. La diferencia esencial es que solo comienza a escuchar 'mousemove' después de 'mousedown', y deja de escuchar 'mousemove' después de '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"/>

Demostración: explicación más completa de la necesidad e implementación de las soluciones anteriores

El siguiente código demuestra más completamente numerosos aspectos de esta estrategia. Las explicaciones están integradas en la demostración:

Andrew Willems
fuente
Muy buena información. ¿Quizás incluso agregar eventos táctiles alguna vez? touchmovedebería ser suficiente, y creo que puede tratarse como mousemoveen este caso.
nick
@nick, mira mi otra respuesta en esta página para obtener una solución diferente que proporciona la funcionalidad del navegador móvil.
Andrew Willems
Esta no es una respuesta accesible, ya que la navegación del teclado no activará los eventos, ya que todos están centrados en el mouse
LocalPCGuy
@LocalPCGuy, tienes razón. Mi respuesta rompió la capacidad de control integrada del teclado de los controles deslizantes. He actualizado el código para restaurar el control del teclado. Si conoce otras formas en que mi código interrumpe la accesibilidad incorporada, hágamelo saber.
Andrew Willems
7

Las soluciones de Andrew Willem no son compatibles con dispositivos móviles.

Aquí hay una modificación de su segunda solución que funciona en Edge, IE, Opera, FF, Chrome, iOS Safari y equivalentes móviles (que podría probar):

Actualización 1: Se eliminó la parte "requestAnimationFrame", ya que estoy de acuerdo en que no es necesario:

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

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

Actualización 2: Respuesta a la respuesta actualizada de Andrew el 2 de junio de 2016:

Gracias, Andrew: parece funcionar en todos los navegadores que pude encontrar (escritorio de Win: IE, Chrome, Opera, FF; Android Chrome, Opera y FF, iOS Safari).

Actualización 3: solución if ("oninput in slider)

Lo siguiente parece funcionar en todos los navegadores anteriores. (No puedo encontrar la fuente original ahora.) Estaba usando esto, pero posteriormente falló en IE y entonces busqué uno diferente, por lo tanto, terminé aquí.

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

Pero antes de verificar su solución, noté que esto estaba funcionando nuevamente en IE, tal vez hubo algún otro conflicto.

MBourne
fuente
1
Confirmé que su solución funciona en dos navegadores de escritorio diversos: Mac Firefox y Windows IE11. No he podido comprobar personalmente ninguna posibilidad móvil. Pero esto parece una gran actualización de mi respuesta que lo amplía para incluir dispositivos móviles. (Supongo que "input" y "change" aceptan tanto la entrada del mouse en un sistema de escritorio como la entrada táctil en un sistema móvil). ¡Un trabajo muy agradable que debería ser ampliamente aplicable y que merece la pena obtener reconocimiento e implementación!
Andrew Willems
1
Después de investigar más, me di cuenta de que su solución tiene varios inconvenientes. Primero, creo que requestAnimationFrame es innecesario. En segundo lugar, el código activa eventos adicionales (al menos 3 eventos, por ejemplo, mouseup, en algunos navegadores). Tercero, los eventos exactos disparados difieren entre algunos navegadores. Por lo tanto, proporcioné una respuesta separada basada en su idea inicial de usar una combinación de eventos de "entrada" y "cambio", lo que le da crédito por la inspiración original.
Andrew Willems
2

Para un buen comportamiento entre navegadores y un código comprensible, lo mejor es usar el atributo onchange en combinación de un formulario:

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 mismo usando oninput , el valor se cambia directamente.

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

Otro enfoque: simplemente establezca un indicador en un elemento que indique qué tipo de evento debe manejarse:

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

Puede utilizar el evento "ondrag" de JavaScript para disparar continuamente. Es mejor que "entrada" debido a las siguientes razones:

  1. Soporte de navegador.

  2. Podría diferenciar entre el evento "ondrag" y el "cambio". "input" dispara tanto para arrastrar como para cambiar.

En jQuery:

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

});

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

Jeque
fuente