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

252

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

Как я могу сделать это при перетаскивании в 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)">

Прасант КЦ
источник
4
Если элемент диапазона имеет фокус, вы можете перемещать ползунок с помощью клавиш со стрелками. И в этом случае также onchangeне срабатывает. Именно в устранении неполадок этой проблемы я нашел этот вопрос.
Сильдорет

Ответы:

452

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

Frederik
источник
113
Или $("#myelement").on("input change", function() { doSomething(); });с помощью jQuery.
Алекс Ванг
19
Просто отметьте, что с этим решением в chrome вы получите два вызова обработчику (по одному на событие), поэтому, если вы заботитесь об этом, вам нужно остерегаться этого.
Хайме
3
У меня были проблемы с тем, что событие «change» не запускается в мобильном chrome (v34), но добавление «input» в уравнение устранило его для меня, но в других местах не оказало вредного воздействия.
brins0
7
@Jaime: « если ты заботишься об этом, то тебе нужно остерегаться этого». Как я могу это сделать, пожалуйста?
2
К сожалению, onchange()не работает в мобильной сети, как Android Chromeи iOS Safari. Любое альтернативное предложение?
Олстон
41

РЕЗЮМЕ:

Здесь я предоставляю кросс-браузерную возможность 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. окончательное наведение мыши (или касание)

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

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

Решение:

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

таблица, показывающая поведение всех браузеров, использующих предложенное решение

В 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, а строка 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

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

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

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

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

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

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

Обновить

По отношению к changeсобытию и inputсобытию, вызывающему срабатывание функции дважды, это в значительной степени не проблема.

Если у вас включена функция input, крайне маловероятно, что при возникновении changeсобытия возникнет проблема .

inputсрабатывает быстро при перетаскивании ползунка ввода диапазона. Беспокоиться о еще одном вызове функции в конце - все равно, что беспокоиться об одной капле воды по сравнению с океаном воды, который является inputсобытиями.

Причина даже включения changeсобытия вообще ради совместимости браузера (в основном с IE).

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

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

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

Описание: кросс-браузерное простое решение 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"/>

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

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

Эндрю Виллемс
источник
Действительно хорошая информация. Возможно, даже добавить в контакт события когда-нибудь? touchmoveдолжно быть достаточно, и я думаю, что это можно рассматривать так же, как mousemoveв этом случае.
Ник
@nick, пожалуйста, смотрите мой другой ответ на этой странице для другого решения, которое обеспечивает функциональность мобильного браузера.
Эндрю Виллемс
Это не доступный ответ, поскольку навигация с помощью клавиатуры не будет
запускать
@LocalPCGuy, ты прав. Мой ответ сломал встроенную клавиатуру-управляемость слайдеров. Я обновил код для восстановления управления с клавиатуры. Если вы знаете какие-либо другие способы, которыми мой код нарушает встроенную доступность, дайте мне знать.
Эндрю Виллемс
7

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

Вот модификация его второго решения, которое работает в 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 - возможно, был какой-то другой конфликт.

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

Для хорошего кросс-браузерного поведения и понятного кода лучше всего использовать атрибут 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>

NVRM
источник
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);
        }
    };
}
Никита
источник
-3

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

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

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

В jQuery:

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

});

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

шейх
источник