onchange event pada tipe input = range tidak memicu di firefox saat menyeret

252

Ketika saya bermain dengan <input type="range">, Firefox memicu acara pertukaran hanya jika kita meletakkan slider ke posisi baru di mana Chrome dan orang lain memicu acara pertukaran saat slider ditarik.

Bagaimana saya bisa mewujudkannya dengan menyeret di 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
sumber
4
Jika elemen rentang memiliki fokus, Anda dapat memindahkan slider menggunakan tombol panah. Dan dalam hal itu, juga, onchangetidak menyala. Itu dalam pemecahan masalah yang saya temukan pertanyaan ini.
Sildoreth

Jawaban:

452

Tampaknya Chrome dan Safari salah: onchangeseharusnya hanya dipicu ketika pengguna melepaskan mouse. Untuk mendapatkan pembaruan terus-menerus, Anda harus menggunakan oninputacara tersebut, yang akan menangkap pembaruan langsung di Firefox, Safari dan Chrome, baik dari mouse dan keyboard.

Namun, oninputtidak didukung di IE10, jadi taruhan terbaik Anda adalah menggabungkan dua penangan acara, seperti ini:

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

Lihat utas Bugzilla ini untuk informasi lebih lanjut.

Frederik
sumber
113
Atau $("#myelement").on("input change", function() { doSomething(); });dengan jQuery.
Alex Vang
19
Perhatikan bahwa dengan solusi ini, di chrome Anda akan mendapatkan dua panggilan ke handler (satu per peristiwa), jadi jika Anda peduli untuk itu, maka Anda harus menjaganya.
Jaime
3
Saya mengalami masalah dengan acara 'perubahan' yang tidak diaktifkan di mobile chrome (v34), tetapi menambahkan 'input' ke dalam persamaan telah memperbaikinya untuk saya, sementara tidak memiliki efek merugikan di tempat lain.
brins0
7
@Jaime: " jika Anda peduli akan hal itu, maka Anda perlu menjaganya. " Bagaimana saya bisa melakukannya, tolong?
2
Sayangnya, onchange()tidak berfungsi di web seluler seperti Android Chromedan iOS Safari. Ada saran alternatif?
Alston
41

RINGKASAN:

Saya berikan di sini kemampuan lintas-peramban desktop dan seluler tanpa jQuery untuk secara konsisten merespons interaksi rentang / penggeser, sesuatu yang tidak mungkin dilakukan di peramban saat ini. Ini pada dasarnya memaksa semua browser untuk meniru on("change"...acara IE11 untuk mereka on("change"...atau on("input"...acara. Fungsi baru adalah ...

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

... di mana relemen input rentang Anda dan fpendengar Anda. Pendengar akan dipanggil setelah interaksi apa pun yang mengubah nilai rentang / penggeser tetapi tidak setelah interaksi yang tidak mengubah nilai itu, termasuk interaksi awal mouse atau sentuh pada posisi penggeser saat ini atau saat memindahkan salah satu ujung penggeser.

Masalah:

Pada awal Juni 2016, berbagai browser berbeda dalam hal bagaimana mereka merespons penggunaan rentang / slider. Lima skenario relevan:

  1. mouse-down awal (atau sentuh-start) pada posisi slider saat ini
  2. mouse-down awal (atau sentuh-start) pada posisi slider baru
  3. gerakan mouse berikutnya (atau sentuh) setelah 1 atau 2 sepanjang slider
  4. gerakan mouse berikutnya (atau sentuh) setelah 1 atau 2 melewati salah satu ujung slider
  5. mouse-up akhir (atau touch-end)

Tabel berikut menunjukkan bagaimana setidaknya tiga browser desktop berbeda dalam perilaku mereka sehubungan dengan skenario di atas yang mereka respons:

tabel yang menunjukkan perbedaan browser sehubungan dengan acara yang mereka respons dan kapan

Larutan:

The onRangeChangeberfungsi memberikan respon lintas-browser konsisten dan dapat diprediksi interaksi berbagai / slider. Itu memaksa semua browser untuk berperilaku sesuai dengan tabel berikut:

tabel yang memperlihatkan perilaku semua browser menggunakan solusi yang diusulkan

Di IE11, kode ini pada dasarnya memungkinkan semuanya beroperasi sesuai status quo, yaitu memungkinkan "change"acara berfungsi dengan cara standar dan "input"acara tidak relevan karena tidak pernah menyala. Di browser lain, "change"acara secara efektif dibungkam (untuk mencegah peristiwa tambahan dan kadang-kadang tidak mudah-muncul dari penembakan). Selain itu, "input"acara ini hanya akan mengaktifkan pendengar ketika nilai rentang / penggeser berubah. Untuk beberapa browser (misalnya Firefox) ini terjadi karena pendengar secara efektif dibungkam dalam skenario 1, 4 dan 5 dari daftar di atas.

(Jika Anda benar-benar membutuhkan pendengar untuk diaktifkan dalam skenario 1, 4 dan / atau 5 Anda dapat mencoba memasukkan "mousedown"/ "touchstart", "mousemove"/ "touchmove"dan / atau "mouseup"/ "touchend"peristiwa. Solusi semacam itu berada di luar cakupan jawaban ini.)

Fungsi di Peramban Seluler:

Saya telah menguji kode ini di peramban desktop tetapi tidak di peramban seluler mana pun. Namun, dalam jawaban lain di halaman ini MBourne telah menunjukkan bahwa solusi saya di sini "... tampaknya berfungsi di setiap browser yang dapat saya temukan (Menangkan desktop: IE, Chrome, Opera, FF; Android Chrome, Opera dan FF, Safari Safari) " . (Terima kasih MBourne.)

Pemakaian:

Untuk menggunakan solusi ini, sertakan onRangeChangefungsi dari ringkasan di atas (disederhanakan / diperkecil) atau cuplikan kode demo di bawah ini (identik secara fungsional tetapi lebih jelas) dalam kode Anda sendiri. Aktifkan sebagai berikut:

onRangeChange(myRangeInputElmt, myListener);

di mana elemen DOM myRangeInputElmtyang Anda inginkan <input type="range">dan myListenermerupakan fungsi pendengar / penangan yang Anda inginkan dipanggil pada "change"peristiwa-seperti.

Pendengar Anda mungkin kurang parameter jika diinginkan atau dapat menggunakan eventparameter, yaitu salah satu dari berikut ini akan berfungsi, tergantung pada kebutuhan Anda:

var myListener = function() {...

atau

var myListener = function(evt) {...

(Menghapus pendengar acara dari inputelemen (misalnya menggunakan removeEventListener) tidak dibahas dalam jawaban ini.)

Deskripsi Demo:

Dalam cuplikan kode di bawah ini, fungsi onRangeChangemenyediakan solusi universal. Sisa kode hanyalah contoh untuk menunjukkan penggunaannya. Setiap variabel yang dimulai dengan my...tidak relevan dengan solusi universal dan hanya hadir untuk kepentingan demo.

Demo ini menunjukkan rentang / nilai slider serta berapa kali standar "change", "input"dan "onRangeChange"acara kustom telah dipecat (masing-masing baris A, B dan C). Saat menjalankan cuplikan ini di browser yang berbeda, perhatikan hal berikut saat Anda berinteraksi dengan rentang / slider:

  • Di IE11, nilai-nilai dalam baris A dan C keduanya berubah dalam skenario 2 dan 3 di atas sementara baris B tidak pernah berubah.
  • Di Chrome dan Safari, nilai dalam baris B dan C keduanya berubah dalam skenario 2 dan 3 sementara baris A hanya berubah untuk skenario 5.
  • Di Firefox, nilai dalam baris A hanya berubah untuk skenario 5, perubahan baris B untuk semua lima skenario, dan perubahan baris C hanya untuk skenario 2 dan 3.
  • Di semua browser di atas, perubahan pada baris C (solusi yang diusulkan) identik, yaitu hanya untuk skenario 2 dan 3.

Kode Demo:

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

Kredit:

Meskipun implementasinya di sini sebagian besar milik saya, itu terinspirasi oleh jawaban MBourne . Jawaban lain itu menyarankan bahwa peristiwa "input" dan "perubahan" dapat digabungkan dan bahwa kode yang dihasilkan akan berfungsi di browser desktop dan seluler. Namun, kode dalam jawaban itu menghasilkan peristiwa "ekstra" tersembunyi yang dipecat, yang dengan sendirinya bermasalah, dan peristiwa yang dipecat berbeda di antara peramban, masalah selanjutnya. Implementasi saya di sini menyelesaikan masalah tersebut.

Kata kunci:

Jenis slider rentang input JavaScript mengubah input browser kompatibilitas-silang mobile desktop no-jQuery

Andrew Willems
sumber
31

Saya memposting ini sebagai jawaban karena itu layak menjadi jawaban itu sendiri daripada komentar di bawah jawaban yang kurang berguna. Saya menemukan metode ini jauh lebih baik daripada jawaban yang diterima karena dapat menyimpan semua js dalam file terpisah dari HTML.

Jawaban diberikan oleh Jamrelian dalam komentarnya di bawah jawaban yang diterima.

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

Namun perlu diperhatikan komentar Jaime ini

Perhatikan bahwa dengan solusi ini, di chrome Anda akan mendapatkan dua panggilan ke handler (satu per peristiwa), jadi jika Anda peduli untuk itu, maka Anda harus menjaganya.

Seperti di dalamnya akan memecat acara ketika Anda telah berhenti menggerakkan mouse, dan kemudian lagi ketika Anda melepaskan tombol mouse.

Memperbarui

Sehubungan dengan changeacara dan inputacara yang menyebabkan fungsi memicu dua kali, ini bukan masalah.

Jika Anda memiliki fungsi untuk mematikan fungsi input, sangat kecil kemungkinannya bahwa akan ada masalah saat changeacara diaktifkan.

inputmenyala dengan cepat saat Anda menarik slider input rentang. Khawatir tentang satu lagi panggilan fungsi menembak pada akhirnya adalah seperti khawatir tentang setetes air dibandingkan dengan lautan air yang merupakan inputperistiwa.

Alasan bahkan termasuk changeacara sama sekali adalah demi kompatibilitas browser (terutama dengan IE).

Daniel Tonon
sumber
plus 1 Untuk satu-satunya solusi yang bekerja dengan internet explorer !! Apakah Anda mengujinya di browser lain juga?
Jessica
1
Ini jQuery sehingga kemungkinan tidak berfungsi cukup rendah. Saya sendiri belum melihatnya tidak bekerja di mana pun. Ia bahkan berfungsi pada ponsel yang bagus.
Daniel Tonon
30

UPDATE: Saya meninggalkan jawaban ini di sini sebagai contoh tentang bagaimana menggunakan peristiwa mouse untuk menggunakan interaksi rentang / slider di browser desktop (tetapi bukan ponsel). Namun, saya sekarang juga menulis jawaban yang sama sekali berbeda dan, saya percaya, jawaban yang lebih baik di bagian lain halaman ini yang menggunakan pendekatan berbeda untuk menyediakan solusi desktop -dan-mobile lintas-browser untuk masalah ini.

Jawaban asli:

Rangkuman: Solusi lintas-browser, JavaScript biasa (yaitu, no-jQuery) untuk memungkinkan nilai input rentang baca tanpa menggunakan on('input'...dan / atau on('change'...yang berfungsi secara tidak konsisten di antara browser.

Sampai hari ini (akhir Februari 2016), masih ada inkonsistensi browser, jadi saya memberikan pekerjaan baru di sini.

Masalahnya: Saat menggunakan input rentang, yaitu bilah geser, on('input'...memberikan nilai rentang yang terus diperbarui di Mac dan Windows Firefox, Chrome dan Opera serta Mac Safari, sementara on('change'...hanya melaporkan nilai rentang saat mouse-up. Sebaliknya, di Internet Explorer (v11), on('input'...tidak berfungsi sama sekali, dan on('change'...terus diperbarui.

Saya melaporkan di sini 2 strategi untuk mendapatkan pelaporan nilai rentang terus menerus yang identik di semua browser menggunakan vanilla JavaScript (yaitu tidak ada jQuery) dengan menggunakan mousedown, mousemove dan (mungkin) peristiwa mouseup.

Strategi 1: Lebih pendek tetapi kurang efisien

Jika Anda lebih suka kode yang lebih pendek daripada kode yang lebih efisien, Anda dapat menggunakan solusi pertama ini yang menggunakan mouse dan mouse tetapi tidak menggunakan mouse. Ini membaca bilah geser sesuai kebutuhan, tetapi terus menembak tanpa perlu selama acara tetikus apa pun, bahkan ketika pengguna belum mengklik dan karenanya tidak menyeret bilah geser. Ini pada dasarnya membaca nilai rentang baik setelah 'mousedown' dan selama acara 'mousemove', sedikit menunda masing-masing menggunakan 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"/>

Strategi 2: Lebih lama tetapi lebih efisien

Jika Anda membutuhkan kode yang lebih efisien dan dapat mentolerir panjang kode yang lebih lama, maka Anda dapat menggunakan solusi berikut yang menggunakan mousedown, mousemove, dan mouseup. Ini juga membaca bilah geser sesuai kebutuhan, tetapi berhenti membacanya segera setelah tombol mouse dilepaskan. Perbedaan mendasar adalah bahwa hanya mulai mendengarkan 'mousemove' setelah 'mousedown', dan berhenti mendengarkan untuk 'mousemove' setelah '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"/>

Demo: Penjelasan lebih lengkap tentang perlunya, dan implementasi, penyelesaian di atas

Kode berikut lebih lengkap menunjukkan banyak aspek dari strategi ini. Penjelasan tertanam dalam demonstrasi:

Andrew Willems
sumber
Info yang sangat bagus. Mungkin bahkan menambahkan acara yang berhubungan kapan-kapan? touchmoveharus cukup, dan saya pikir itu bisa diperlakukan seperti mousemovedalam kasus ini.
nick
@nick, silakan lihat jawaban saya yang lain di halaman ini untuk solusi berbeda yang menyediakan fungsionalitas peramban seluler.
Andrew Willems
Ini bukan jawaban yang dapat diakses, karena navigasi keyboard tidak akan memecat acara karena semuanya adalah mouse centric
LocalPCGuy
@LocalPCGuy, Anda benar. Jawaban saya memecahkan built-in keyboard-controllability dari slider. Saya telah memperbarui kode untuk memulihkan kontrol keyboard. Jika Anda mengetahui cara lain kode saya memecah aksesibilitas bawaan, beri tahu saya.
Andrew Willems
7

Solusi Andrew Willem tidak kompatibel untuk perangkat seluler.

Berikut ini adalah modifikasi dari solusi keduanya yang bekerja di Edge, IE, Opera, FF, Chrome, iOS Safari, dan perangkat seluler (yang dapat saya uji):

Pembaruan 1: Bagian "requestAnimationFrame" yang dihapus, karena saya setuju itu tidak perlu:

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

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

Pembaruan 2: Respon untuk jawaban terbaru Andrew 2 Juni 2016:

Terima kasih, Andrew - yang tampaknya berfungsi di setiap browser yang dapat saya temukan (Menangkan desktop: IE, Chrome, Opera, FF; Android Chrome, Opera dan FF, Safari Safari).

Pembaruan 3: solusi if ("oninput in slider)

Tampaknya ini berfungsi di semua browser di atas. (Saya tidak dapat menemukan sumber aslinya sekarang.) Saya menggunakan ini, tetapi kemudian gagal pada IE dan saya mencari yang lain, maka saya berakhir di sini.

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

Tetapi sebelum saya memeriksa solusi Anda, saya perhatikan ini berfungsi lagi di IE - mungkin ada beberapa konflik lainnya.

MBourne
sumber
1
Saya mengonfirmasi bahwa solusi Anda berfungsi di dua browser desktop yang beragam: Mac Firefox dan Windows IE11. Saya pribadi belum dapat memeriksa kemungkinan seluler apa pun. Tapi ini sepertinya pembaruan yang bagus untuk jawaban saya yang memperluasnya dengan memasukkan perangkat seluler. (Saya berasumsi bahwa "input" dan "perubahan" menerima input mouse pada sistem desktop dan input sentuh pada sistem seluler.) Pekerjaan yang sangat bagus yang harus diterapkan secara luas dan layak mendapatkan pengakuan dan implementasi!
Andrew Willems
1
Setelah menggali lebih lanjut, saya menyadari bahwa solusi Anda memiliki beberapa kelemahan. Pertama, saya yakin requestAnimationFrame tidak perlu. Kedua, kode ini memunculkan acara tambahan (setidaknya 3 peristiwa, misalnya mouseup, di beberapa browser). Ketiga, peristiwa persis yang dipecat berbeda di antara beberapa browser. Karena itu saya memberikan jawaban terpisah berdasarkan ide awal Anda menggunakan kombinasi peristiwa "input" dan "perubahan", memberi Anda penghargaan atas inspirasi orisinalnya.
Andrew Willems
2

Untuk perilaku lintas-browser yang baik, dan kode yang dapat dimengerti, yang terbaik adalah menggunakan atribut onchange dalam kombinasi formulir:

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>

Menggunakan oninput yang sama , nilainya diubah secara langsung.

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

Namun pendekatan lain - hanya menetapkan bendera pada elemen yang menandakan jenis acara apa yang harus ditangani:

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

Anda dapat menggunakan acara JavaScript "ondrag" untuk memecat secara terus-menerus. Itu lebih baik daripada "input" karena alasan berikut:

  1. Dukungan browser.

  2. Dapat membedakan antara acara "ondrag" dan "perubahan". "input" menyala untuk seret dan ubah.

Di jQuery:

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

});

Referensi: http://www.w3schools.com/TAgs/ev_ondrag.asp

Syekh
sumber