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

217

Когда я играл с <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)">

SCRIPT

function showVal(newVal){
  document.getElementById("valBox").innerHTML=newVal;
}
  • 4
    Если элемент диапазона имеет фокус, вы можете перемещать ползунок с помощью клавиш со стрелками. И в этом случае также onchange не срабатывает. Именно в устранении неполадок этой проблемы я нашел этот вопрос.
Теги:
onchange
input
firefox

8 ответов

360
Лучший ответ

По-видимому, 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 для получения дополнительной информации.

  • 99
    Или $("#myelement").on("input change", function() { doSomething(); }); с помощью jQuery.
  • 17
    Просто отметьте, что с этим решением в chrome вы получите два вызова обработчику (по одному на событие), поэтому, если вы заботитесь об этом, вам необходимо принять меры предосторожности.
Показать ещё 4 комментария
29

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

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

Описание: кросс-браузерное простое решение 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'... постоянно обновляется).

Здесь я сообщаю о двух стратегиях получения одинаковых отчетов о значениях непрерывного диапазона во всех браузерах с использованием стандартного 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>
  • 0
    Действительно хорошая информация. Возможно, даже добавить в контакт события когда-нибудь? touchmove должно быть достаточным, и я думаю, что в этом случае его можно рассматривать как mousemove .
  • 0
    @nick, пожалуйста, смотрите мой другой ответ на этой странице для другого решения, которое обеспечивает функциональность мобильного браузера.
Показать ещё 2 комментария
26

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

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

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

Просто знай об этом комментарии Хайме, хотя

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

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

  • 0
    плюс 1 Для единственного решения, которое работает с Internet Explorer! Вы тестировали это и в других браузерах?
  • 1
    Это jQuery, так что шансы не работать довольно низки. Я никогда не видел, чтобы это нигде не работало. Это даже работает на мобильном телефоне, и это здорово.
25

РЕЗЮМЕ:

Я предоставляю здесь возможность работы с несколькими браузерами без jQuery для последовательного реагирования на взаимодействие диапазона/слайдера, что невозможно в текущих браузерах. Это по сути заставляет все браузеры эмулировать событие IE11 on("change"... для своих событий 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 вдоль слайдера
  • любое последующее движение мыши (или касания) после 1 или 2 мимо любого конца слайдера
  • конечная кнопка мыши (или сенсорная панель)

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

Изображение 5031

Решение:

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

Изображение 5032

В 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 - ваш желаемый элемент <input type="range"> DOM, а 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, а строка А изменяется только для сценария 5.
  • В Firefox значение в строке А изменяется только для сценария 5, изменения строки В для всех пяти сценариев, а строка С изменяется только для сценариев 2 и 3.
  • Во всех вышеперечисленных браузерах изменения в строке C (предлагаемое решение) идентичны, то есть только для сценариев 2 и 3.

Демо-код:

// main function for emulating IE11 "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. Этот другой ответ предполагает, что события "ввода" и "изменение" могут быть объединены и что полученный код будет работать как в настольных, так и в мобильных браузерах. Тем не менее, код в этом ответе приводит к скрытым "дополнительным" событиям, которые увольняются, что само по себе является проблематичным, а события, выпущенные разными, отличаются между браузерами, что является еще одной проблемой. Моя реализация здесь решает эти проблемы.

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

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

4

Решения Andrew Willem не совместимы с мобильными устройствами.

Вот модификация его второго решения, которое работает в 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: ответ Andrew 2 июня 2016 года обновленный ответ:

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

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

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

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

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

  • 1
    Я подтвердил, что ваше решение работает в двух разных браузерах для настольных компьютеров: Mac Firefox и Windows IE11. Я лично не смог проверить какие-либо мобильные возможности. Но это похоже на большое обновление моего ответа, которое расширяет его и включает в себя мобильные устройства. (Я предполагаю, что «ввод» и «изменение» допускают как ввод с помощью мыши в настольной системе, так и сенсорный ввод в мобильной системе.) Очень хорошая работа, которая должна широко применяться и заслуживать признания и реализации!
  • 1
    Покопавшись дальше, я понял, что у вашего решения есть несколько недостатков. Во-первых, я считаю, что requestAnimationFrame не нужен. Во-вторых, код запускает дополнительные события (по крайней мере, 3 события, например, mouseup, в некоторых браузерах). В-третьих, точные события отличаются в разных браузерах. Таким образом, я предоставил отдельный ответ, основанный на вашей первоначальной идее об использовании комбинации событий «вход» и «изменение», что дает вам кредит на первоначальное вдохновение.
0

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

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

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

Это решение только для html/javascript, а также может использоваться в режиме онлайн.

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

Вы можете использовать JavaScript-событие "ondrag" для непрерывного запуска. Это лучше, чем "enter" по следующим причинам:

  1. Поддержка браузера.

  2. Может различать события ondrag и change. "вход" запускается как для перетаскивания, так и для изменения.

В jQuery:

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

});

Ссылка: http://www.w3schools.com/tags/ev_ondrag.asp

Ещё вопросы

Сообщество Overcoder
Наверх
Меню