it-roy-ru.com

Событие onchange для типа ввода = диапазон не запускается в Firefox при перетаскивании

Когда я играл с <input type="range">, Firefox запускает событие onchange, только если мы переместим ползунок на новую позицию, где Chrome и ​​другие запускают события onchange, когда ползунок перемещается.

Как я могу сделать это при перетаскивании в Firefox?

HTML

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

СКРИПТ

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

Очевидно, Chrome и ​​Safari неверны: onchange должен запускаться только тогда, когда пользователь отпускает мышь. Чтобы получать постоянные обновления, вы должны использовать событие oninput, которое будет захватывать живые обновления в Firefox, Safari и Chrome, как с помощью мыши, так и с клавиатуры.

Однако oninput не поддерживается в IE10, поэтому лучше всего объединить два обработчика событий, например так:

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

Проверьте это Bugzilla тема для получения дополнительной информации.

403
Frederik

РЕЗЮМЕ:

Здесь я предоставляю кросс-браузерную возможность no-jQuery для настольных и мобильных устройств, позволяющую последовательно реагировать на взаимодействия диапазона/ползунка, что невозможно в современных браузерах. По сути, это заставляет все браузеры эмулировать событие on("change"... в IE11 для их событий on("change"... или on("input".... Новая функция ...

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

... где r - ваш элемент ввода диапазона, а f - ваш слушатель. Слушатель будет вызываться после любого взаимодействия, которое изменяет значение диапазона/ползунка, но не после взаимодействий, которые не изменяют это значение, включая начальные взаимодействия мыши или касания в текущей позиции ползунка или после перемещения с любого конца ползунка.

Проблема:

По состоянию на начало июня 2016 года разные браузеры различаются по тому, как они реагируют на использование диапазона/слайдера. Пять сценариев актуальны:

  1. начальное нажатие мыши (или сенсорный запуск) в текущей позиции ползунка
  2. начальное нажатие мыши (или сенсорный запуск) в новой позиции ползунка
  3. любое последующее движение мыши (или касание) после 1 или 2 вдоль ползунка
  4. любое последующее движение мыши (или касание) после 1 или 2 после любого конца ползунка
  5. окончательное наведение мыши (или касание)

В следующей таблице показано, как, по крайней мере, три разных настольных браузера отличаются по своему поведению в зависимости от того, на какой из приведенных выше сценариев они реагируют:

table showing browser differences with respect to which events they respond to and when

Решение:

Функция onRangeChange обеспечивает согласованный и предсказуемый кроссбраузерный ответ на взаимодействия диапазона/ползунка. Это заставляет все браузеры вести себя согласно следующей таблице:

table showing behaviour of all browsers using the proposed solution

В IE11 код, по сути, позволяет всему работать в соответствии со статус-кво, то есть он позволяет событию "change" функционировать стандартным образом, а событие "input" не имеет значения, так как оно никогда не срабатывает. В других браузерах событие "change" эффективно отключается (для предотвращения запуска дополнительных, а иногда и не очевидных) событий. Кроме того, событие "input" запускает слушателя только при изменении значения диапазона/ползунка. В некоторых браузерах (например, Firefox) это происходит потому, что слушатель эффективно отключается в сценариях 1, 4 и 5 из приведенного выше списка.

(Если вы действительно хотите, чтобы прослушиватель был активирован в любом из сценариев 1, 4 и/или 5, вы можете попробовать включить "mousedown"/"touchstart", "mousemove"/"touchmove" и/или "mouseup"/"touchend". Такое решение является выходит за рамки этого ответа.)

Функциональность в мобильных браузерах:

Я тестировал этот код в настольных браузерах, но не в мобильных браузерах. Тем не менее, в другой ответ на этой странице MBourne показал, что мое решение здесь "..., похоже, работает в любом браузере, который я смог найти (Win desktop: IE, Chrome, Opera, FF; Android Chrome, Opera и ​​FF, iOS Safari) ". (Спасибо, MBourne.)

Использование:

Чтобы использовать это решение, включите в свой код функцию onRangeChange из приведенного выше краткого описания (упрощенный/минимизированный) или приведенный ниже фрагмент демонстрационного кода (функционально идентичный, но более понятный). Вызовите это следующим образом:

onRangeChange(myRangeInputElmt, myListener);

где myRangeInputElmt - это желаемый элемент DOM <input type="range">, а myListener - функция прослушивателя/обработчика, которую вы хотите вызывать при "change"-подобных событиях.

При желании ваш слушатель может быть без параметров или может использовать параметр event, т. Е. Может работать любое из следующих действий, в зависимости от ваших потребностей:

var myListener = function() {...

или же

var myListener = function(evt) {...

(Удаление прослушивателя событий из элемента input (например, с использованием removeEventListener) в этом ответе не рассматривается.)

Описание демо:

В приведенном ниже фрагменте кода функция onRangeChange предоставляет универсальное решение. Остальная часть кода является просто примером, демонстрирующим его использование. Любая переменная, которая начинается с my..., не имеет отношения к универсальному решению и присутствует только для демонстрации.

Демонстрация показывает значение диапазона/ползунка, а также количество срабатываний стандартных событий "change", "input" и пользовательских "onRangeChange" (строки A, B и C соответственно). При запуске этого фрагмента в разных браузерах обратите внимание на следующее при взаимодействии с диапазоном/ползунком:

  • В IE11 значения в строках A и C изменяются в сценариях 2 и 3 выше, в то время как строка B никогда не изменяется.
  • В Chrome и ​​Safari значения в строках B и C изменяются в сценариях 2 и 3, а строка A изменяется только для сценария 5.
  • В Firefox значение в строке A изменяется только для сценария 5, строка B изменяется для всех пяти сценариев, а строка C изменяется только для сценариев 2 и 3.
  • Во всех вышеперечисленных браузерах изменения в строке C (предлагаемое решение) идентичны, т. Е. Только для сценариев 2 и 3.

Демо-код:

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

Кредит:

Хотя реализация здесь в значительной степени моя собственная, она была вдохновлена ​​ ответ MBourne . Этот другой ответ предполагал, что события "input" и "change" можно объединить и что полученный код будет работать как в настольных, так и в мобильных браузерах. Однако код в этом ответе приводит к запуску скрытых "лишних" событий, что само по себе проблематично, а возникающие события различаются в разных браузерах, что является еще одной проблемой. Моя реализация здесь решает эти проблемы.

Ключевые слова:

События ползунка диапазона ввода типа JavaScript изменяют совместимость ввода в браузере кросс-браузерный рабочий стол mobile no-jQuery

31
Andrew Willems

ОБНОВЛЕНИЕ: я оставляю здесь этот ответ в качестве примера того, как использовать события мыши для использования взаимодействий диапазона/ползунка в настольных (но не мобильных) браузерах. Тем не менее, теперь я также написал совершенно другой и, я полагаю, лучший ответ в другом месте на этой странице, где используется другой подход к обеспечению кроссбраузерного рабочего стола - и - мобильное решение этой проблемы.

Оригинальный ответ:

Краткое описание: кросс-браузерное простое решение JavaScript (то есть no-jQuery), позволяющее считывать входные значения диапазона без использования on('input'... и/или on('change'..., которые несовместимы между браузерами.

На сегодняшний день (конец февраля 2016 г.) браузер по-прежнему не согласован, поэтому я предлагаю новый способ решения проблемы.

Проблема: при использовании ввода диапазона, то есть ползунка, on('input'... предоставляет постоянно обновляемые значения диапазона в Mac и Windows Firefox, Chrome и ​​Opera, а также в Mac Safari, тогда как on('change'... только сообщает значение диапазона при наведении мыши. Напротив, в Internet Explorer (v11) on('input'... не работает вообще, а on('change'... постоянно обновляется.

Здесь я сообщаю о двух стратегиях получения одинаковых отчетов о значениях непрерывного диапазона во всех браузерах с использованием Vanilla JavaScript (т.е. без jQuery) с использованием событий mousedown, mousemove и (возможно) mouseup.

Стратегия 1: короче, но менее эффективно

Если вы предпочитаете более короткий код более эффективному, вы можете использовать это первое решение, которое использует mousesdown и mousemove, но не mouseup. Это читает ползунок по мере необходимости, но продолжает без необходимости срабатывать при любых событиях при наведении курсора, даже если пользователь не нажал и, следовательно, не перетаскивает ползунок. По сути, он считывает значение диапазона как после событий 'mousedown', так и во время событий 'mousemove', слегка задерживая каждое использование 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"/>

Стратегия 2: дольше, но эффективнее

Если вам нужен более эффективный код и вы можете терпеть большую длину кода, то вы можете использовать следующее решение, которое использует mousedown, mousemove и mouseup. Это также читает ползунок по мере необходимости, но соответствующим образом прекращает читать его, как только кнопка мыши отпущена. Существенным отличием является то, что прослушивание "mousemove" начинается только после "mousedown", а прослушивание "mousemove" после "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"/>

Демонстрация: более полное объяснение необходимости и реализации вышеуказанных обходных путей

Следующий код более полно демонстрирует многочисленные аспекты этой стратегии. Пояснения включены в демонстрацию:

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

Я публикую это как ответ, потому что он заслуживает того, чтобы быть его собственным ответом, а не комментарием под менее полезным ответом. Я считаю, что этот метод намного лучше, чем принятый ответ, поскольку он может хранить все js в отдельном файле из HTML.

Ответ предоставил Джамрелиан в своем комментарии под принятым ответом.

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

Просто будьте в курсе этого комментария Хайме, хотя

Просто отметьте, что с этим решением в chrome вы получите два вызова обработчику (по одному на событие), поэтому, если вы заботитесь об этом, вам нужно принять меры против него.

Как и в нем будет срабатывать событие, когда вы перестали двигать мышь, а затем снова, когда вы отпустите кнопку мыши.

26
Daniel Tonon

Решения Эндрю Виллема не совместимы с мобильными устройствами.

Вот модификация его второго решения, которое работает в Edge, IE, Opera, FF, Chrome, iOS Safari и мобильных эквивалентах (которые я мог протестировать):

Обновление 1: удалена часть requestAnimationFrame, поскольку я согласен, что в этом нет необходимости:

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

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

Обновление 2: Ответ на обновленный ответ Эндрю 2 июня 2016 года:

Спасибо, Эндрю, похоже, он работает в любом браузере, который я смог найти (Win desktop: IE, Chrome, Opera, FF; Android Chrome, Opera и ​​FF, iOS Safari).

Обновление 3: решение if ("oninput in slider)

Следующее, кажется, работает во всех вышеупомянутых браузерах. (Я не могу найти исходный источник сейчас.) Я использовал это, но впоследствии он не удался на IE, и поэтому я пошел искать другой, поэтому я оказался здесь.

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

Но прежде чем я проверил ваше решение, я заметил, что это снова работает в IE - возможно, был какой-то другой конфликт.

4
MBourne

Для хорошего кросс-браузерного поведения и понятного кода лучше всего использовать атрибут onchange в комбинации формы:

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>

То же самое, используя oninput, значение изменяется напрямую.

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>
2
Cryptopat

Еще один подход - просто установить флаг на элементе, указывающий, какой тип события должен быть обработан:

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