evento onchange no tipo de entrada = range não está sendo acionado no firefox enquanto arrasta

252

Quando brinquei <input type="range">, o Firefox dispara um evento de troca apenas se colocarmos o controle deslizante em uma nova posição em que o Chrome e outros acionam eventos de troca enquanto o controle deslizante é arrastado.

Como posso fazer isso arrastando no 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
fonte
4
Se o elemento do intervalo tiver foco, você pode mover o controle deslizante usando as teclas de seta. E nesse caso, também, o onchangenão dispara. Foi na solução desse problema que encontrei esta pergunta.
Sildoreth

Respostas:

452

Aparentemente, o Chrome e o Safari estão errados: onchangesó devem ser acionados quando o usuário soltar o mouse. Para obter atualizações contínuas, você deve usar o oninputevento, que irá capturar atualizações ao vivo no Firefox, Safari e Chrome, tanto do mouse quanto do teclado.

No entanto, oninputnão é suportado no IE10, portanto, sua melhor aposta é combinar os dois manipuladores de eventos, assim:

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

Confira este tópico do Bugzilla para obter mais informações.

Frederik
fonte
113
Ou $("#myelement").on("input change", function() { doSomething(); });com jQuery.
Alex Vang
19
Observe que, com esta solução, no chrome, você receberá duas chamadas para o manipulador (uma por evento); portanto, se você se importar com isso, precisará se proteger.
Jaime
3
Eu estava tendo problemas com o evento 'change' que não era acionado no Chrome móvel (v34), mas adicionar 'input' à equação o corrigiu para mim, sem causar efeitos negativos em outros lugares.
usar o seguinte comando
7
@ Jaime: " se você se importa com isso, precisa se proteger. " Como posso fazer isso, por favor?
2
Infelizmente, onchange()não funciona na Web móvel como Android Chromee iOS Safari. Alguma sugestão alternativa?
Alston
41

RESUMO:

Fornecemos aqui a capacidade de desktop e celular no navegador sem jQuery para responder consistentemente a interações de intervalo / controle deslizante, algo que não é possível nos navegadores atuais. É essencialmente obriga todos os navegadores para emular do IE11 on("change"...evento, quer para os seus on("change"...ou on("input"...eventos. A nova função é ...

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

... onde restá seu elemento de entrada de intervalo e fé seu ouvinte. O ouvinte será chamado após qualquer interação que altere o valor do intervalo / controle deslizante, mas não após as interações que não alteram esse valor, incluindo as interações iniciais do mouse ou toque na posição atual do controle deslizante ou ao sair de qualquer extremidade do controle deslizante.

Problema:

No início de junho de 2016, diferentes navegadores diferem em termos de como eles respondem ao uso do intervalo / controle deslizante. Cinco cenários são relevantes:

  1. inicial do mouse para baixo (ou toque inicial) na posição atual do controle deslizante
  2. inicial do mouse para baixo (ou toque inicial) em uma nova posição do controle deslizante
  3. qualquer movimento subsequente do mouse (ou toque) após 1 ou 2 ao longo do controle deslizante
  4. qualquer movimento subsequente do mouse (ou toque) após 1 ou 2 após a extremidade do controle deslizante
  5. mouse final (ou touch-end) final

A tabela a seguir mostra como pelo menos três navegadores de desktop diferentes diferem em seu comportamento em relação a quais dos cenários acima eles respondem:

tabela mostrando as diferenças do navegador em relação a quais eventos eles respondem e quando

Solução:

A onRangeChangefunção fornece uma resposta consistente e previsível entre navegadores às interações de intervalo / controle deslizante. Força todos os navegadores a se comportarem de acordo com a tabela a seguir:

tabela mostrando o comportamento de todos os navegadores usando a solução proposta

No IE11, o código essencialmente permite que tudo opere conforme o status quo, ou seja, permite que o "change"evento funcione da maneira padrão e o "input"evento é irrelevante, pois nunca é acionado. Em outros navegadores, o "change"evento é efetivamente silenciado (para impedir que eventos extras e às vezes não-aparentes sejam disparados). Além disso, o"input" evento aciona seu ouvinte apenas quando o valor do intervalo / controle deslizante é alterado. Para alguns navegadores (por exemplo, Firefox), isso ocorre porque o ouvinte é efetivamente silenciado nos cenários 1, 4 e 5 da lista acima.

(Se você realmente deseja que um ouvinte seja ativado nos cenários 1, 4 e / ou 5, tente incorporar "mousedown"/ "touchstart", "mousemove"/ "touchmove"e / ou "mouseup"/ "touchend"eventos. Essa solução está além do escopo desta resposta.)

Funcionalidade em navegadores móveis:

Testei esse código em navegadores de desktop, mas não em navegadores móveis. No entanto, em outra resposta desta página, o MBourne mostrou que minha solução aqui "... parece funcionar em todos os navegadores que eu encontrei (Win desktop: IE, Chrome, Opera, FF; Android Chrome, Opera e FF, iOS Safari) " . (Obrigado MBourne.)

Uso:

Para usar esta solução, inclua a onRangeChangefunção do resumo acima (simplificado / minificado) ou o trecho de código de demonstração abaixo (funcionalmente idêntico, mas mais auto-explicativo) em seu próprio código. Chame da seguinte maneira:

onRangeChange(myRangeInputElmt, myListener);

onde myRangeInputElmtestá o <input type="range">elemento DOM desejado e myListeneré a função de ouvinte / manipulador que você deseja chamar em "change"eventos semelhantes.

Seu ouvinte pode ter menos parâmetros, se desejado, ou pode usar o eventparâmetro, ou seja, qualquer um dos seguintes procedimentos funcionará, dependendo de suas necessidades:

var myListener = function() {...

ou

var myListener = function(evt) {...

(Remover o ouvinte de evento do inputelemento (por exemplo, usar removeEventListener) não é abordado nesta resposta.)

Descrição da demonstração:

No trecho de código abaixo, a função onRangeChangefornece a solução universal. O restante do código é simplesmente um exemplo para demonstrar seu uso. Qualquer variável que comece com my...é irrelevante para a solução universal e está presente apenas para fins de demonstração.

Os shows de demonstração do valor / intervalo de controle deslizante, bem como o número de vezes que o padrão "change", "input"e personalizados "onRangeChange"eventos têm disparados (linhas A, B e C, respectivamente). Ao executar esse snippet em diferentes navegadores, observe o seguinte ao interagir com o intervalo / controle deslizante:

  • No IE11, os valores nas linhas A e C mudam nos cenários 2 e 3 acima, enquanto a linha B nunca muda.
  • No Chrome e Safari, os valores nas linhas B e C mudam nos cenários 2 e 3, enquanto a linha A muda apenas para o cenário 5.
  • No Firefox, o valor na linha A é alterado apenas para o cenário 5, a linha B é alterada para todos os cinco cenários e a linha C é alterada apenas para os cenários 2 e 3.
  • Em todos os navegadores acima, as alterações na linha C (a solução proposta) são idênticas, ou seja, apenas para os cenários 2 e 3.

Código de demonstração:

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

Embora a implementação aqui seja em grande parte minha, foi inspirada na resposta de MBourne . Essa outra resposta sugeriu que os eventos "input" e "change" pudessem ser mesclados e que o código resultante funcionaria nos navegadores de desktop e móvel. No entanto, o código nessa resposta resulta no acionamento de eventos "extras" ocultos, o que por si só é problemático e os eventos acionados diferem entre os navegadores, um problema adicional. Minha implementação aqui resolve esses problemas.

Palavras-chave:

Eventos de controle deslizante de intervalo do tipo de entrada JavaScript alteram a compatibilidade do navegador de entrada desktop no navegador móvel no-jQuery

Andrew Willems
fonte
31

Estou postando isso como uma resposta, porque merece ser sua própria resposta, em vez de um comentário em uma resposta menos útil. Acho esse método muito melhor do que a resposta aceita, pois pode manter todos os js em um arquivo separado do HTML.

Resposta fornecida por Jamrelian em seu comentário sob a resposta aceita.

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

Apenas esteja ciente deste comentário de Jaime

Observe que, com esta solução, no chrome, você receberá duas chamadas para o manipulador (uma por evento); portanto, se você se importar com isso, precisará se proteger.

Como ele disparará o evento quando você parar de mover o mouse e, novamente, quando você soltar o botão do mouse.

Atualizar

Em relação ao changeevento e ao inputevento que causou o acionamento da funcionalidade duas vezes, isso é praticamente um problema.

Se você tiver uma função ativando input, é extremamente improvável que haja um problema quando o changeevento for disparado.

inputdispara rapidamente quando você arrasta um controle deslizante de entrada de alcance. Preocupar-se com mais um disparo de chamada de função no final é como se preocupar com uma única gota de água em comparação com o oceano de água que é o inputevento.

O motivo de incluir o changeevento mesmo é por uma questão de compatibilidade do navegador (principalmente com o IE).

Daniel Tonon
fonte
mais 1 Para a única solução que funciona com o Internet Explorer !! Você também testou em outros navegadores?
22415 Jessica
1
É jQuery, então as chances de ele não funcionar são bem baixas. Eu não vi isso funcionar em lugar nenhum. Até funciona no celular, o que é ótimo.
Daniel Tonon
30

ATUALIZAÇÃO: Estou deixando esta resposta aqui como um exemplo de como usar eventos de mouse para usar interações de intervalo / controle deslizante em navegadores de desktop (mas não em dispositivos móveis). No entanto, agora também escrevi uma resposta completamente diferente e, acredito, melhor em outras partes desta página que usa uma abordagem diferente para fornecer uma solução de desktop e móvel em vários navegadores para esse problema.

Resposta original:

Resumo: Uma solução JavaScript simples para vários navegadores (isto é, no-jQuery) para permitir a leitura dos valores de entrada do intervalo sem usar on('input'...e / ou on('change'...que funcionam inconsistentemente entre navegadores.

A partir de hoje (final de fevereiro de 2016), ainda há inconsistência no navegador, portanto estou fornecendo uma nova solução alternativa aqui.

O problema: ao usar uma entrada de intervalo, ou seja, um controle deslizante, on('input'...fornece valores de intervalo atualizados continuamente no Mac e Windows Firefox, Chrome e Opera, bem como no Mac Safari, enquanto on('change'...apenas informa o valor do intervalo após o mouse. Por outro lado, no Internet Explorer (v11), on('input'...não funciona e on('change'...é atualizado continuamente.

Relato aqui duas estratégias para obter relatórios de valores de faixa contínua idênticos em todos os navegadores usando JavaScript vanilla (isto é, sem jQuery) usando os eventos mousedown, mousemove e (possivelmente) mouseup.

Estratégia 1: Mais curta, mas menos eficiente

Se você preferir um código mais curto do que um código mais eficiente, poderá usar esta primeira solução que usa mousesdown e mousemove, mas não mouseup. Isso lê o controle deslizante conforme necessário, mas continua disparando desnecessariamente durante eventos de mouse-over, mesmo quando o usuário não clicou e, portanto, não está arrastando o controle deslizante. Ele basicamente lê o valor do intervalo depois dos eventos 'mousedown' e durante 'mousemove', atrasando levemente 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"/>

Estratégia 2: Mais longa, mas mais eficiente

Se você precisar de um código mais eficiente e puder tolerar um tamanho maior, poderá usar a solução a seguir, que usa redução de mouse, remoção de mouse e mouse. Isso também lê o controle deslizante, conforme necessário, mas interrompe-o adequadamente assim que o botão do mouse é liberado. A diferença essencial é que isso só começa a ouvir 'mousemove' após 'mousedown' e para de ouvir 'mousemove' apó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"/>

Demonstração: explicação mais completa da necessidade e implementação das soluções alternativas acima

O código a seguir demonstra mais detalhadamente vários aspectos dessa estratégia. As explicações são incorporadas na demonstração:

Andrew Willems
fonte
Informação realmente boa. Talvez até adicione eventos de toque em algum momento? touchmovedeve ser suficiente e acho que pode ser tratado como mousemoveneste caso.
nick
@ Nick, por favor, veja minha outra resposta nesta página para uma solução diferente que fornece a funcionalidade do navegador móvel.
Andrew Willems 03/06
Esta não é uma resposta acessível, pois a navegação pelo teclado não aciona os eventos, pois eles são todos centralizados no mouse
LocalPCGuy
@LocalPCGuy, você está certo. Minha resposta quebrou a capacidade de controlar o teclado dos controles deslizantes. Eu atualizei o código para restaurar o controle do teclado. Se você souber de outras maneiras em que meu código quebra a acessibilidade interna, entre em contato.
Andrew Willems
7

As soluções de Andrew Willem não são compatíveis com dispositivos móveis.

Aqui está uma modificação de sua segunda solução que funciona no Edge, IE, Opera, FF, Chrome, iOS Safari e equivalentes móveis (que eu poderia testar):

Atualização 1: Remoção da parte "requestAnimationFrame", pois concordo que não é necessário:

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

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

Atualização 2: resposta à resposta atualizada de 2 de junho de 2016 de Andrew:

Obrigado, Andrew - que parece funcionar em todos os navegadores que pude encontrar (Win desktop: IE, Chrome, Opera, FF; Android Chrome, Opera e FF, iOS Safari).

Atualização 3: solução if ("oninput in slider)

O seguinte parece funcionar em todos os navegadores acima. (Não consigo encontrar a fonte original agora.) Eu estava usando isso, mas posteriormente falhou no IE e, portanto, procurei uma fonte diferente, portanto acabei aqui.

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

Porém, antes de verificar sua solução, notei que isso estava funcionando novamente no IE - talvez houvesse algum outro conflito.

MBourne
fonte
1
Confirmei que sua solução funciona em dois navegadores de desktop diversos: Mac Firefox e Windows IE11. Pessoalmente, não fui capaz de verificar nenhuma possibilidade móvel. Mas isso parece uma ótima atualização na minha resposta que a amplia para incluir dispositivos móveis. (Presumo que "entrada" e "alteração" aceitem tanto a entrada do mouse em um sistema de desktop quanto a entrada de toque em um sistema móvel.) Um trabalho muito bom que deve ser amplamente aplicável e vale a pena obter reconhecimento e implementação!
Andrew Willems 03/06
1
Depois de pesquisar mais, percebi que sua solução tem várias desvantagens. Primeiro, acredito que o requestAnimationFrame é desnecessário. Segundo, o código dispara eventos extras (pelo menos 3 eventos após, por exemplo, mouseup, em alguns navegadores). Terceiro, os eventos exatos disparados diferem entre alguns navegadores. Assim, forneci uma resposta separada com base em sua ideia inicial de usar uma combinação de eventos "input" e "change", dando crédito à inspiração original.
Andrew Willems
2

Para um bom comportamento entre navegadores e código compreensível, o melhor é usar o atributo onchange na combinação de um formulário:

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>

O mesmo usando oninput , o valor é alterado diretamente.

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

Ainda outra abordagem - basta definir um sinalizador em um elemento sinalizando que tipo de evento deve ser tratado:

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

Você pode usar o evento "ondrag" do JavaScript para disparar continuamente. É melhor que "input" devido aos seguintes motivos:

  1. Suporte ao navegador.

  2. Pode diferenciar entre o evento "ondrag" e "change". "input" é acionado para arrastar e alterar.

No jQuery:

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

});

Referência: http://www.w3schools.com/TAgs/ev_ondrag.asp

Xeque
fonte