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

252

Lorsque je jouais avec <input type="range">, Firefox déclenche un événement onchange uniquement si nous déposons le curseur dans une nouvelle position où Chrome et d'autres déclenchent des événements onchange pendant que le curseur est déplacé.

Comment puis-je y arriver en faisant glisser dans 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
la source
4
Si l'élément de plage a le focus, vous pouvez déplacer le curseur à l'aide des touches fléchées. Et dans ce cas également, le onchangene se déclenche pas. C'est en résolvant ce problème que j'ai trouvé cette question.
Sildoreth

Réponses:

452

Apparemment, Chrome et Safari ont tort: onchangene doivent être déclenchés que lorsque l'utilisateur relâche la souris. Pour obtenir des mises à jour continues, vous devez utiliser l' oninputévénement, qui capturera les mises à jour en direct dans Firefox, Safari et Chrome, à la fois à partir de la souris et du clavier.

Cependant, oninputn'est pas pris en charge dans IE10, donc votre meilleur pari est de combiner les deux gestionnaires d'événements, comme ceci:

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

Consultez ce fil Bugzilla pour plus d'informations.

Frederik
la source
113
Ou $("#myelement").on("input change", function() { doSomething(); });avec jQuery.
Alex Vang
19
Notez simplement qu'avec cette solution, en chrome, vous recevrez deux appels au gestionnaire (un par événement), donc si vous vous souciez de cela, vous devez vous prémunir contre cela.
Jaime
3
J'avais des problèmes avec l'événement 'change' qui ne se déclenchait pas dans le chrome mobile (v34), mais l'ajout de 'input' dans l'équation l'a corrigé pour moi, tout en n'ayant aucun effet néfaste ailleurs.
brins0
7
@Jaime: " si vous vous souciez de cela, alors vous devez vous prémunir contre cela. " Comment puis-je le faire, s'il vous plaît?
2
Malheureusement, onchange()ne fonctionne pas sur le Web mobile comme Android Chromeet iOS Safari. Une autre suggestion?
Alston
41

RÉSUMÉ:

Je fournis ici une capacité de bureau et mobile multi-navigateur sans jQuery pour répondre de manière cohérente aux interactions plage / curseur, ce qui n'est pas possible dans les navigateurs actuels. Il oblige essentiellement tous les navigateurs à émuler l' on("change"...événement IE11 pour leur on("change"...ou leurs on("input"...événements. La nouvelle fonction est ...

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

... où rest votre élément d'entrée de plage et fvotre auditeur. L'auditeur sera appelé après toute interaction qui modifie la valeur de la plage / du curseur, mais pas après les interactions qui ne modifient pas cette valeur, y compris les interactions initiales de la souris ou du toucher à la position actuelle du curseur ou lors du déplacement de l'une ou l'autre extrémité du curseur.

Problème:

Au début de juin 2016, les différents navigateurs diffèrent en termes de réponse à l'utilisation de la plage / du curseur. Cinq scénarios sont pertinents:

  1. initial de la souris (ou démarrage tactile) à la position actuelle du curseur
  2. déplacement initial de la souris (ou démarrage tactile) à une nouvelle position du curseur
  3. tout mouvement ultérieur de la souris (ou du toucher) après 1 ou 2 le long du curseur
  4. tout mouvement ultérieur de la souris (ou du toucher) après 1 ou 2 après chaque extrémité du curseur
  5. souris finale (ou tactile)

Le tableau suivant montre comment au moins trois navigateurs de bureau différents diffèrent dans leur comportement en ce qui concerne les scénarios ci-dessus auxquels ils répondent:

tableau montrant les différences de navigateur par rapport aux événements auxquels ils répondent et quand

Solution:

La onRangeChangefonction fournit une réponse inter-navigateur cohérente et prévisible aux interactions plage / curseur. Il oblige tous les navigateurs à se comporter conformément au tableau suivant:

tableau montrant le comportement de tous les navigateurs utilisant la solution proposée

Dans IE11, le code permet essentiellement à tout de fonctionner selon le statu quo, c'est-à-dire qu'il permet à l' "change"événement de fonctionner de manière standard et que l' "input"événement n'a pas d'importance car il ne se déclenche jamais de toute façon. Dans d'autres navigateurs, l' "change"événement est effectivement réduit au silence (pour empêcher le déclenchement d'événements supplémentaires et parfois non apparents). De plus, l' "input"événement ne déclenche son écouteur que lorsque la valeur de la plage / du curseur change. Pour certains navigateurs (par exemple Firefox), cela se produit car l'auditeur est effectivement désactivé dans les scénarios 1, 4 et 5 de la liste ci-dessus.

(Si vous avez vraiment besoin qu'un écouteur soit activé dans les scénarios 1, 4 et / ou 5, vous pouvez essayer d'incorporer les événements "mousedown"/ "touchstart", "mousemove"/ "touchmove"et / ou "mouseup"/ "touchend". Une telle solution dépasse le cadre de cette réponse.)

Fonctionnalité dans les navigateurs mobiles:

J'ai testé ce code dans les navigateurs de bureau mais pas dans les navigateurs mobiles. Cependant, dans une autre réponse sur cette page, MBourne a montré que ma solution ici "... semble fonctionner dans tous les navigateurs que j'ai pu trouver (bureau Win: IE, Chrome, Opera, FF; Android Chrome, Opera et FF, iOS Safari) " . (Merci MBourne.)

Usage:

Pour utiliser cette solution, incluez la onRangeChangefonction du résumé ci-dessus (simplifiée / minifiée) ou l'extrait de code de démonstration ci-dessous (fonctionnellement identique mais plus explicite) dans votre propre code. Appelez-le comme suit:

onRangeChange(myRangeInputElmt, myListener);

myRangeInputElmtest votre <input type="range">élément DOM souhaité et myListenerest la fonction d'écoute / gestionnaire que vous souhaitez invoquer lors d' "change"événements similaires.

Votre auditeur peut être sans paramètre si vous le souhaitez ou peut utiliser le eventparamètre, c'est-à-dire que l'un des éléments suivants fonctionnerait, selon vos besoins:

var myListener = function() {...

ou

var myListener = function(evt) {...

(La suppression de l'écouteur d'événements de l' inputélément (par exemple en utilisant removeEventListener) n'est pas traitée dans cette réponse.)

Description de la démonstration:

Dans l'extrait de code ci-dessous, la fonction onRangeChangefournit la solution universelle. Le reste du code est simplement un exemple pour démontrer son utilisation. Toute variable qui commence par my...n'est pas pertinente pour la solution universelle et n'est présente que pour la démonstration.

La démonstration montre la plage / valeur du curseur ainsi que le nombre de fois la norme "change", "input"et sur mesure des "onRangeChange"événements ont tiré (lignes A, B et C respectivement). Lorsque vous exécutez cet extrait dans différents navigateurs, notez les points suivants lorsque vous interagissez avec la plage / le curseur:

  • Dans IE11, les valeurs des lignes A et C changent toutes les deux dans les scénarios 2 et 3 ci-dessus tandis que la ligne B ne change jamais.
  • Dans Chrome et Safari, les valeurs des lignes B et C changent toutes les deux dans les scénarios 2 et 3 tandis que la ligne A change uniquement pour le scénario 5.
  • Dans Firefox, la valeur de la ligne A ne change que pour le scénario 5, la ligne B change pour les cinq scénarios et la ligne C ne change que pour les scénarios 2 et 3.
  • Dans tous les navigateurs ci-dessus, les modifications de la ligne C (la solution proposée) sont identiques, c'est-à-dire uniquement pour les scénarios 2 et 3.

Code de démonstration:

// 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édit:

Bien que l'implémentation ici soit largement la mienne, elle a été inspirée par la réponse de MBourne . Cette autre réponse suggérait que les événements «entrée» et «changement» pouvaient être fusionnés et que le code résultant fonctionnerait à la fois sur les navigateurs de bureau et mobiles. Cependant, le code dans cette réponse entraîne le déclenchement d'événements "supplémentaires" cachés, ce qui en soi est problématique, et les événements déclenchés diffèrent entre les navigateurs, un autre problème. Ma mise en œuvre ici résout ces problèmes.

Mots clés:

Événements du curseur de plage de type d'entrée JavaScript changer la compatibilité du navigateur d'entrée cross-browser desktop mobile no-jQuery

Andrew Willems
la source
31

Je poste cela comme une réponse, car il 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 peut conserver tous les js dans un fichier séparé du HTML.

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

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

Mais soyez conscient de ce commentaire de Jaime

Notez simplement qu'avec cette solution, en chrome, vous recevrez deux appels au gestionnaire (un par événement), donc si vous vous souciez de cela, vous devez vous prémunir contre cela.

Comme dans, il déclenchera l'événement lorsque vous aurez arrêté de déplacer la souris, puis à nouveau lorsque vous relâcherez le bouton de la souris.

Mettre à jour

En ce qui concerne l' changeévénement et l' inputévénement provoquant le déclenchement de la fonctionnalité deux fois, il s'agit à peu près d'un problème.

Si une fonction est activée input, il est extrêmement peu probable qu'il y ait un problème lorsque l' changeévénement se déclenche.

inputse déclenche rapidement lorsque vous faites glisser un curseur d'entrée de plage. S'inquiéter d'un appel de fonction supplémentaire à la fin, c'est comme s'inquiéter d'une seule goutte d'eau par rapport à l'océan d'eau qui est les inputévénements.

La raison de même d'inclure l' changeévénement est pour la compatibilité du navigateur (principalement avec IE).

Daniel Tonon
la source
plus 1 Pour la seule solution qui fonctionne avec Internet Explorer !! L'avez-vous également testé sur d'autres navigateurs?
Jessica
1
C'est jQuery donc les chances qu'il ne fonctionne pas sont assez faibles. Je ne l'ai pas vu fonctionner nulle part moi-même. Cela fonctionne même sur mobile, ce qui est génial.
Daniel Tonon
30

MISE À JOUR: Je laisse cette réponse ici comme un exemple de la façon d'utiliser les événements de souris pour utiliser les interactions plage / curseur dans les navigateurs de bureau (mais pas mobiles). Cependant, j'ai également écrit une réponse complètement différente et, je crois, meilleure ailleurs dans cette page qui utilise une approche différente pour fournir une solution de bureau et mobile multi-navigateur à ce problème.

Réponse originale:

Résumé: Une solution JavaScript multi-navigateur simple (c'est-à-dire no-jQuery) pour permettre la lecture des valeurs d'entrée de plage sans utiliser on('input'...et / ou on('change'...qui fonctionnent de manière incohérente entre les navigateurs.

À ce jour (fin février 2016), il y a toujours une incohérence du navigateur, donc je propose une nouvelle solution ici.

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

Je rapporte ici 2 stratégies pour obtenir des rapports de valeurs de plage continue identiques dans tous les navigateurs utilisant JavaScript vanille (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 à un code plus efficace, vous pouvez utiliser cette 1ère solution qui utilise mousesdown et mousemove mais pas mouseup. Cela lit le curseur selon les besoins, mais continue de tirer inutilement pendant les événements de survol, même lorsque l'utilisateur n'a pas cliqué et ne fait donc pas glisser le curseur. Il lit essentiellement la valeur de la plage à la fois après «mousedown» et pendant les événements «mousemove», retardant légèrement chaque utilisation 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 pouvez tolérer une longueur de code plus longue, vous pouvez utiliser la solution suivante qui utilise mousedown, mousemove et mouseup. Cela lit également le curseur selon les besoins, mais arrête correctement sa lecture dès que le bouton de la souris est relâché. La différence essentielle est qu'il ne commence à écouter «mousemove» qu'après «mousedown», et qu'il arrête 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 de contournement ci-dessus

Le code suivant illustre plus en détail de nombreux aspects de cette stratégie. Des explications sont intégrées dans la démonstration:

Andrew Willems
la source
Vraiment de bonnes informations. Peut-être même ajouter des événements en contact un jour? touchmovedevrait être suffisant, et je pense qu'il peut être traité comme mousemovedans ce cas.
nick
@nick, veuillez consulter mon autre réponse sur cette page pour une solution différente qui offre une fonctionnalité de navigateur mobile.
Andrew Willems
Ce n'est pas une réponse accessible, car la navigation au clavier ne déclenchera pas les événements car ils sont tous centrés sur la souris
LocalPCGuy
@LocalPCGuy, vous avez raison. Ma réponse a brisé la contrôlabilité du clavier intégrée des curseurs. J'ai mis à jour le code pour restaurer le contrôle du clavier. Si vous connaissez d'autres façons dont mon code rompt l'accessibilité intégrée, faites-le moi savoir.
Andrew Willems
7

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 reconnais 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 mise à jour du 2 juin 2016 d'Andrew:

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: solution if ("oninput in slider)

Les éléments suivants semblent fonctionner sur tous les navigateurs ci-dessus. (Je ne trouve pas la source d'origine maintenant.) J'utilisais cela, mais cela a échoué par la suite sur IE et j'ai donc cherché une autre, donc je me suis retrouvé ici.

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.

MBourne
la source
1
J'ai confirmé que votre solution fonctionne dans deux navigateurs de bureau différents: Mac Firefox et Windows IE11. Je n'ai personnellement pu vérifier aucune possibilité de téléphonie mobile. Mais cela ressemble à une excellente mise à jour de ma réponse qui l'élargit pour inclure les appareils mobiles. (Je suppose que «entrée» et «changement» acceptent à la fois l'entrée de la souris sur un système de bureau et l'entrée tactile sur un système mobile.) Très beau travail qui devrait être largement applicable et mérite d'être reconnu et mis en œuvre!
Andrew Willems du
1
Après avoir creusé davantage, j'ai réalisé que votre solution présente plusieurs inconvénients. Tout d'abord, je pense que le requestAnimationFrame n'est pas nécessaire. Deuxièmement, le code déclenche des événements supplémentaires (au moins 3 événements lors, par exemple mouseup, dans certains navigateurs). Troisièmement, les événements exacts déclenchés diffèrent entre certains navigateurs. J'ai donc fourni une réponse distincte basée sur votre idée initiale d'utiliser une combinaison d'événements «entrée» et «changement», vous donnant le crédit de l'inspiration originale.
Andrew Willems
2

Pour un bon comportement entre navigateurs et un code compréhensible, le mieux est d'utiliser l'attribut onchange en combinaison d'un formulaire:

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>

De même en utilisant oninput , la valeur est modifiée directement.

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
la source
0

Encore une autre approche - il suffit de définir un indicateur sur un élément pour signaler 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);
        }
    };
}
Nikita
la source
-3

Vous pouvez utiliser l'événement JavaScript "ondrag" pour tirer en continu. C'est mieux que "entrée" pour les raisons suivantes:

  1. Prise en charge du navigateur.

  2. Pourrait faire la différence entre l'événement «ondrag» et l'événement «change». "input" se déclenche à la fois par glisser-déplacer.

Dans jQuery:

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

});

Référence: http://www.w3schools.com/TAgs/ev_ondrag.asp

Cheik
la source