web-dev-qa-db-fra.com

événement onchange sur type d'entrée = la plage ne se déclenche pas dans firefox lors du glissement

Lorsque je joue avec <input type="range">, Firefox déclenche un événement onchange uniquement si nous plaçons le curseur à une nouvelle position où Chrome et d'autres déclenchent des événements onchange lorsque le curseur est déplacé.

Comment puis-je y arriver en faisant glisser Firefox?

HTML

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

SCRIPT

function showVal(newVal){
  document.getElementById("valBox").innerHTML=newVal;
}
213
Prasanth K C

Apparemment, Chrome et Safari sont incorrects: onchange ne devrait être déclenché que lorsque l'utilisateur relâche la souris. Pour obtenir des mises à jour continues, vous devez utiliser l'événement oninput, qui capture les mises à jour en direct dans Firefox, Safari et Chrome, à la fois à l'aide de la souris et du clavier.

Cependant, oninput n'est pas pris en charge dans IE10. Il est donc préférable de combiner les deux gestionnaires d'événements, comme suit:

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

Consultez ce fil de discussion Bugzilla pour plus d'informations.

390
Frederik

UPDATE: Je laisse ici cette réponse comme exemple d'utilisation des événements de souris pour utiliser les interactions plage/curseur dans les navigateurs de bureau (mais non mobiles). Cependant, j’ai aussi maintenant écrit une réponse tout à fait différente et, je crois, meilleure réponse ailleurs sur cette page qui utilise une approche différente pour fournir une solution de bureau_et-mobile inter-navigateur problème.

Réponse originale:

Résumé: solution JavaScript (par exemple, non-jQuery) multi-navigateurs permettant de lire les valeurs d'entrée de plage sans utiliser on('input'... et/ou on('change'..., qui fonctionnent de manière incohérente entre les navigateurs.

À compter d'aujourd'hui (fin février 2016), il y a toujours une incohérence dans le navigateur. Je propose donc une nouvelle solution ici.

Le problème: lorsqu’une entrée de plage, c’est-à-dire un curseur, on('input'... fournit des valeurs de plage constamment mises à jour dans Mac et Windows Firefox, Chrome et Opera ainsi que Mac Safari, tandis que on('change'... ne signale la valeur de plage qu’au passage de la souris. En revanche, dans Internet Explorer (v11), on('input'... ne fonctionne pas du tout et on('change'... est mis à jour en permanence.

Je présente ici deux stratégies pour obtenir un rapport identique sur la valeur de la plage continue dans tous les navigateurs utilisant JavaScript Vanilla (c'est-à-dire sans jQuery) en utilisant les événements mousedown, mousemove et (éventuellement) mouseup.

Stratégie 1: plus courte mais moins efficace

Si vous préférez un code plus court qu'un code plus efficace, vous pouvez utiliser cette 1ère solution qui utilise mousesdown et mousemove mais pas mouseup. Ceci lit le curseur si nécessaire, mais continue de se déclencher inutilement lors d'un événement de survol de la souris, même lorsque l'utilisateur n'a pas cliqué et ne fait donc pas glisser le curseur. Il lit essentiellement la valeur de plage à la fois après le "mousedown" et pendant les événements "mousemove", en retardant légèrement chacun en utilisant 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"/>

Stratégie 2: plus longue mais plus efficace

Si vous avez besoin d'un code plus efficace et que vous pouvez tolérer une longueur de code plus longue, vous pouvez utiliser la solution suivante, qui utilise la souris, le souris, et la souris. Cela lit également le curseur si nécessaire, mais cesse de le lire dès que vous relâchez le bouton de la souris. La différence essentielle réside dans le fait qu’il commence seulement à écouter «mousemove» après «mousedown» et qu’il cesse d’écouter «mousemove» après «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"/>

Démo: Explication plus complète de la nécessité et de la mise en œuvre des solutions ci-dessus

Le code suivant illustre de manière plus complète de nombreux aspects de cette stratégie. Les explications sont intégrées à la démonstration:

var select, inp, listen, unlisten, anim, show, onInp, onChg, onDn1, onDn2, onMv1, onMv2, onUp, onMvCombo1, onDnCombo1, onUpCombo2, onMvCombo2, onDnCombo2;

select   = function(selctr)     { return document.querySelector(selctr);      };
inp = select("input");
listen   = function(evtTyp, cb) { return inp.   addEventListener(evtTyp, cb); };
unlisten = function(evtTyp, cb) { return inp.removeEventListener(evtTyp, cb); };
anim     = function(cb)         { return window.requestAnimationFrame(cb);    };
show = function(id) {
	return function() {
    select("#" + id + " td~td~td"   ).innerHTML = inp.value;
    select("#" + id + " td~td~td~td").innerHTML = (Math.random() * 1e20).toString(36); // random text
  };
};

onInp      =                  show("inp" )                                      ;
onChg      =                  show("chg" )                                      ;
onDn1      =                  show("mdn1")                                      ;
onDn2      = function() {anim(show("mdn2"));                                   };
onMv1      =                  show("mmv1")                                      ;
onMv2      = function() {anim(show("mmv2"));                                   };
onUp       =                  show("mup" )                                      ;
onMvCombo1 = function() {anim(show("cmb1"));                                   };
onDnCombo1 = function() {anim(show("cmb1"));   listen("mousemove", onMvCombo1);};
onUpCombo2 = function() {                    unlisten("mousemove", onMvCombo2);};
onMvCombo2 = function() {anim(show("cmb2"));                                   };
onDnCombo2 = function() {anim(show("cmb2"));   listen("mousemove", onMvCombo2);};

listen("input"    , onInp     );
listen("change"   , onChg     );
listen("mousedown", onDn1     );
listen("mousedown", onDn2     );
listen("mousemove", onMv1     );
listen("mousemove", onMv2     );
listen("mouseup"  , onUp      );
listen("mousedown", onDnCombo1);
listen("mousedown", onDnCombo2);
listen("mouseup"  , onUpCombo2);
table {border-collapse: collapse; font: 10pt Courier;}
th, td {border: solid black 1px; padding: 0 0.5em;}
input {margin: 2em;}
li {padding-bottom: 1em;}
<p>Click on 'Full page' to see the demonstration properly.</p>
<table>
  <tr><th></th><th>event</th><th>range value</th><th>random update indicator</th></tr>
  <tr id="inp" ><td>A</td><td>input                                </td><td>100</td><td>-</td></tr>
  <tr id="chg" ><td>B</td><td>change                               </td><td>100</td><td>-</td></tr>
  <tr id="mdn1"><td>C</td><td>mousedown                            </td><td>100</td><td>-</td></tr>
  <tr id="mdn2"><td>D</td><td>mousedown using requestAnimationFrame</td><td>100</td><td>-</td></tr>
  <tr id="mmv1"><td>E</td><td>mousemove                            </td><td>100</td><td>-</td></tr>
  <tr id="mmv2"><td>F</td><td>mousemove using requestAnimationFrame</td><td>100</td><td>-</td></tr>
  <tr id="mup" ><td>G</td><td>mouseup                              </td><td>100</td><td>-</td></tr>
  <tr id="cmb1"><td>H</td><td>mousedown/move combo                 </td><td>100</td><td>-</td></tr>
  <tr id="cmb2"><td>I</td><td>mousedown/move/up combo              </td><td>100</td><td>-</td></tr>
</table>
<input type="range" min="100" max="999" value="100"/>
<ol>
  <li>The 'range value' column shows the value of the 'value' attribute of the range-type input, i.e. the slider. The 'random update indicator' column shows random text as an indicator of whether events are being actively fired and handled.</li>
  <li>To see browser differences between input and change event implementations, use the slider in different browsers and compare A and&nbsp;B.</li>
  <li>To see the importance of 'requestAnimationFrame' on 'mousedown', click a new location on the slider and compare C&nbsp;(incorrect) and D&nbsp;(correct).</li>
  <li>To see the importance of 'requestAnimationFrame' on 'mousemove', click and drag but do not release the slider, and compare E&nbsp;(often 1&nbsp;pixel behind) and F&nbsp;(correct).</li>
  <li>To see why an initial mousedown is required (i.e. to see why mousemove alone is insufficient), click and hold but do not drag the slider and compare E&nbsp;(incorrect), F&nbsp;(incorrect) and H&nbsp;(correct).</li>
  <li>To see how the mouse event combinations can provide a work-around for continuous update of a range-type input, use the slider in any manner and note whichever of A or B continuously updates the range value in your current browser. Then, while still using the slider, note that H and I provide the same continuously updated range value readings as A or B.</li>
  <li>To see how the mouseup event reduces unnecessary calculations in the work-around, use the slider in any manner and compare H and&nbsp;I. They both provide correct range value readings. However, then ensure the mouse is released (i.e. not clicked) and move it over the slider without clicking and notice the ongoing updates in the third table column for H but not&nbsp;I.</li>
</ol>

29
Andrew Willems

Je publie ceci comme une réponse parce qu'elle mérite d'être sa propre réponse plutôt qu'un commentaire sous une réponse moins utile. Je trouve cette méthode bien meilleure que la réponse acceptée car elle permet de conserver tous les fichiers js dans un fichier distinct du code HTML.

Réponse fournie par Jamrelian dans son commentaire sous la réponse acceptée.

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

Juste être conscient de ce commentaire de Jaime si

Notez simplement qu'avec cette solution, en chrome, vous obtiendrez deux appels au gestionnaire (un par événement), donc si vous en tenez à cela, vous devez vous en protéger.

Dans ce cas, l'événement est déclenché lorsque vous avez cessé de déplacer la souris, puis à nouveau lorsque vous relâchez le bouton de la souris.

25
Daniel Tonon

Les solutions d'Andrew Willem ne sont pas compatibles avec les appareils mobiles. 

Voici une modification de sa deuxième solution qui fonctionne dans Edge, IE, Opera, FF, Chrome, iOS Safari et équivalents mobiles (que j'ai pu tester):

Mise à jour 1: suppression de la partie "requestAnimationFrame", car je conviens que ce n'est pas nécessaire:  

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

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

Mise à jour 2: Réponse à la réponse d'Andrew du 2 juin 2016:  

Merci, Andrew - cela semble fonctionner dans tous les navigateurs que j'ai pu trouver (bureau Win: IE, Chrome, Opera, FF; Android Chrome, Opera et FF, iOS Safari).

Mise à jour 3: si (solution "oninput in slider")

Ce qui suit semble fonctionner sur tous les navigateurs ci-dessus. (Je ne peux pas trouver la source originale pour le moment.) J'utilisais ceci, mais cela a échoué par la suite sur IE et je suis donc allé en chercher un autre.

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

Mais avant de vérifier votre solution, j'ai remarqué que cela fonctionnait à nouveau dans IE - il y avait peut-être un autre conflit.

4
MBourne

Pour un bon comportement inter-navigateur, avec un code moins compréhensible, le mieux consiste à utiliser l'attribut onchange en combinaison avec un formulaire:

Ceci est une solution uniquement HTML/JavaScript, et peut également être utilisé en ligne.

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

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

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

1
Cryptopat

Encore une autre approche - il suffit de définir un indicateur sur un élément indiquant le type d'événement à gérer:

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