Memecat peristiwa pada perubahan kelas CSS di jQuery

240

Bagaimana saya bisa menjalankan suatu acara jika kelas CSS ditambahkan atau diubah menggunakan jQuery? Apakah perubahan kelas CSS memicu change()acara jQuery ?

pengguna237060
sumber
8
7 tahun kemudian, dengan adopsi MutationObservers yang cukup luas di browser modern, jawaban yang diterima di sini harus benar-benar diperbarui menjadi Mr Br's .
joelmdev
Ini mungkin bisa membantu Anda. stackoverflow.com/questions/2157963/…
PSR
Dalam penyelesaian jawaban Jason, saya menemukan ini: stackoverflow.com/a/19401707/1579667
Benj

Jawaban:

223

Setiap kali Anda mengubah kelas dalam skrip Anda, Anda bisa menggunakan a triggeruntuk meningkatkan acara Anda sendiri.

$(this).addClass('someClass');
$(mySelector).trigger('cssClassChanged')
....
$(otherSelector).bind('cssClassChanged', data, function(){ do stuff });

tetapi sebaliknya, tidak, tidak ada cara untuk memecat suatu peristiwa ketika kelas berubah. change()hanya menyala setelah fokus meninggalkan input yang inputnya telah diubah.

$(function() {
  var button = $('.clickme')
      , box = $('.box')
  ;
  
  button.on('click', function() { 
    box.removeClass('box');
    $(document).trigger('buttonClick');
  });
            
  $(document).on('buttonClick', function() {
    box.text('Clicked!');
  });
});
.box { background-color: red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="box">Hi</div>
<button class="clickme">Click me</button>

Info lebih lanjut tentang Pemicu jQuery

Jason
sumber
4
Apa masalahnya memicu acara yang kemudian memanggil fungsi? Mengapa tidak memanggil fungsi secara langsung, alih-alih memicu?
RamboNo5
43
Pemicu adalah mekanisme yang memungkinkan elemen tertentu untuk "berlangganan" ke suatu acara. dengan cara itu, ketika acara dipicu, semua peristiwa itu dapat terjadi sekaligus dan menjalankan fungsinya masing-masing. misalnya, jika saya memiliki satu objek yang berubah dari merah menjadi biru dan tiga objek yang menunggu untuk berubah, ketika itu berubah, saya hanya dapat memicu changeColoracara tersebut, dan semua objek yang berlangganan acara itu dapat bereaksi sesuai.
Jason
1
Agak aneh bahwa OP ingin mendengarkan acara 'cssClassChanged'; tentunya kelas ditambahkan sebagai hasil dari beberapa acara 'aplikasi' lainnya seperti 'colourChanged' yang akan lebih masuk akal untuk diumumkan dengan pemicu karena saya tidak dapat membayangkan mengapa ada sesuatu yang tertarik pada perubahan className secara khusus, ini lebih merupakan hasil dari classNameChange pasti?
riscarrott
Jika itu secara khusus perubahan className yang menarik maka saya akan membayangkan dekorasi jQuery.fn.addClass untuk memicu 'cssClassChanged' akan lebih masuk akal seperti @micred berkata
riscarrott
5
@riscarrott saya tidak berani tahu aplikasi OP. Saya hanya mempresentasikan konsep pub / sub dan mereka bisa menerapkannya sesuka mereka. Membantah apa nama acara sebenarnya dengan jumlah info yang diberikan konyol.
Jason
140

IMHO solusi yang lebih baik adalah menggabungkan dua jawaban oleh @ RamboNo5 dan @Jason

Maksud saya mengesampingkan fungsi addClass dan menambahkan acara kustom yang disebut cssClassChanged

// Create a closure
(function(){
    // Your base, I'm in it!
    var originalAddClassMethod = jQuery.fn.addClass;

    jQuery.fn.addClass = function(){
        // Execute the original method.
        var result = originalAddClassMethod.apply( this, arguments );

        // trigger a custom event
        jQuery(this).trigger('cssClassChanged');

        // return the original result
        return result;
    }
})();

// document ready function
$(function(){
    $("#YourExampleElementID").bind('cssClassChanged', function(){ 
        //do stuff here
    });
});
Arash Milani
sumber
Ini memang terdengar seperti pendekatan terbaik, tetapi meskipun saya mengikat acara hanya untuk "#YourExampleElementID", tampaknya semua perubahan kelas (yaitu, pada salah satu elemen di halaman saya) ditangani oleh penangan ini ... ada pemikiran?
Filipe Correia
Ini berfungsi dengan baik untuk saya @FipeipeCorreia. Bisakah Anda memberikan jsfiddle replikasi kasus Anda?
Arash Milani
@ Goldentoa11 Anda harus meluangkan waktu sore atau akhir pekan dan memantau semua yang terjadi pada pageload khas yang banyak menggunakan iklan. Anda akan terpesona oleh volume skrip yang mencoba (kadang-kadang tidak berhasil dengan cara yang paling spektakuler) untuk melakukan hal-hal seperti ini. Saya telah menjalankan beberapa halaman yang memunculkan puluhan peristiwa DOMNodeInserted / DOMSubtreeModified per detik, total ratusan peristiwa pada saat Anda mendapatkan $(), ketika tindakan nyata dimulai.
L0j1k
Anda mungkin juga ingin mendukung pemicu acara yang sama di removeClass
Darius
4
Arash, saya kira saya mengerti mengapa @FilipeCorreia mengalami beberapa masalah dengan pendekatan ini: jika Anda mengikat cssClassChangedacara ini ke suatu elemen, Anda harus menyadari bahwa itu akan dipecat bahkan ketika anaknya mengubah kelas mereka. Karenanya, jika misalnya Anda tidak ingin memecat acara ini untuk mereka, tambahkan saja sesuatu seperti $("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });setelah bind Anda. Bagaimanapun, solusi yang sangat bagus, terima kasih!
tonix
59

Jika Anda ingin mendeteksi perubahan kelas, cara terbaik adalah menggunakan Pengamat Mutasi, yang memberi Anda kontrol penuh atas perubahan atribut apa pun. Namun Anda harus mendefinisikan pendengar sendiri, dan menambahkannya ke elemen yang Anda dengarkan. Untung Anda tidak perlu memicu apa pun secara manual setelah pendengar ditambahkan.

$(function() {
(function($) {
    var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;

    $.fn.attrchange = function(callback) {
        if (MutationObserver) {
            var options = {
                subtree: false,
                attributes: true
            };

            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(e) {
                    callback.call(e.target, e.attributeName);
                });
            });

            return this.each(function() {
                observer.observe(this, options);
            });

        }
    }
})(jQuery);

//Now you need to append event listener
$('body *').attrchange(function(attrName) {

    if(attrName=='class'){
            alert('class changed');
    }else if(attrName=='id'){
            alert('id changed');
    }else{
        //OTHER ATTR CHANGED
    }

});
});

Dalam contoh ini pendengar acara ditambahkan ke setiap elemen, tetapi Anda tidak ingin itu dalam kebanyakan kasus (menghemat memori). Tambahkan pendengar "attrchange" ini ke elemen yang ingin Anda amati.

Tuan Br
sumber
1
ini sepertinya melakukan hal yang persis sama dengan jawaban yang diterima, tetapi dengan lebih banyak kode, tidak didukung pada beberapa browser, dan dengan fleksibilitas yang lebih sedikit. satu-satunya manfaat yang tampaknya dimiliki adalah ia akan menyala setiap kali ada perubahan pada halaman, yang benar-benar akan menghancurkan kinerja Anda ...
Jason
10
Pernyataan Anda dalam jawaban salah @Json, ini sebenarnya cara untuk melakukannya jika Anda tidak ingin mengaktifkan pemicu apa pun secara manual, tetapi aktifkan secara otomatis. Ini bukan hanya manfaat, tetapi juga manfaat, karena ini dipertanyakan. Kedua, ini hanya sebuah contoh, dan seperti yang dikatakan dalam contoh itu tidak disarankan untuk menambahkan pendengar acara ke setiap elemen tetapi hanya elemen yang ingin Anda dengarkan, jadi saya tidak mengerti mengapa itu akan merusak kinerja. Dan hal terakhir, ini adalah masa depan, sebagian besar browser terbaru mendukungnya, caniuse.com/#feat=mutationobserver. Harap pertimbangkan kembali edit, dengan cara yang benar.
Tn. Br
1
The insertionQ perpustakaan memungkinkan Anda menangkap DOM bening muncul jika mereka cocok pemilih. Ini memiliki dukungan browser yang lebih luas karena tidak digunakan DOMMutationObserver.
Dan Dascalescu
Ini solusi hebat. Khususnya untuk memulai dan menghentikan animasi kanvas atau SVG ketika sebuah kelas ditambahkan atau dihapus. Dalam kasus saya untuk memastikan mereka tidak berjalan ketika digulirkan keluar dari pandangan. Manis!
BBaysinger
1
Untuk 2019, ini harus menjadi jawaban yang diterima. Semua peramban besar tampaknya mendukung Pengamat Mutasi sekarang, dan solusi ini tidak perlu memadamkan acara secara manual atau menulis ulang fungsionalitas jQuery dasar.
sp00n
53

Saya sarankan Anda mengganti fungsi addClass. Anda bisa melakukannya dengan cara ini:

// Create a closure
(function(){
    // Your base, I'm in it!
    var originalAddClassMethod = jQuery.fn.addClass;

    jQuery.fn.addClass = function(){
        // Execute the original method.
        var result = originalAddClassMethod.apply( this, arguments );

        // call your function
        // this gets called everytime you use the addClass method
        myfunction();

        // return the original result
        return result;
    }
})();

// document ready function
$(function(){
    // do stuff
});
RamboNo5
sumber
16
Menggabungkan ini dengan $ Jason (mySelector) .trigger ('cssClassChanged') akan menjadi pendekatan terbaik yang saya pikir.
kosoant
4
Ada sebuah plugin yang melakukan ini secara umum: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Jerph
37
Tampaknya ini cara yang bagus untuk membingungkan seorang pengelola masa depan.
roufamatic
1
Jangan lupa untuk mengembalikan hasil dari metode asli.
Petah
1
Untuk referensi, Anda menggunakan Pola Proxy en.wikipedia.org/wiki/Proxy_pattern
Darius
22

Hanya bukti konsep:

Lihatlah intinya untuk melihat beberapa anotasi dan tetap terbarui:

https://gist.github.com/yckart/c893d7db0f49b1ea4dfb

(function ($) {
  var methods = ['addClass', 'toggleClass', 'removeClass'];

  $.each(methods, function (index, method) {
    var originalMethod = $.fn[method];

    $.fn[method] = function () {
      var oldClass = this[0].className;
      var result = originalMethod.apply(this, arguments);
      var newClass = this[0].className;

      this.trigger(method, [oldClass, newClass]);

      return result;
    };
  });
}(window.jQuery || window.Zepto));

Penggunaannya cukup sederhana, cukup tambahkan listender baru pada node yang ingin Anda amati dan memanipulasi kelas seperti biasanya:

var $node = $('div')

// listen to class-manipulation
.on('addClass toggleClass removeClass', function (e, oldClass, newClass) {
  console.log('Changed from %s to %s due %s', oldClass, newClass, e.type);
})

// make some changes
.addClass('foo')
.removeClass('foo')
.toggleClass('foo');
yaart
sumber
Ini bekerja untuk saya, kecuali saya tidak bisa membaca kelas lama atau baru dengan metode removeClass.
slashdottir
1
@ slashdottir Bisakah Anda membuat biola dan menjelaskan apa yang sebenarnya tidak berhasil .
yckart
Ya ... tidak bekerja. TypeError: ini [0] tidak terdefinisi dalam «kembalikan ini [0] .className;»
Pedro Ferreira
itu bekerja tetapi kadang-kadang (mengapa?), this[0]tidak terdefinisi ... cukup ganti ujung baris dengan var oldClassdan var newClassdengan tugas berikut:= (this[0] ? this[0].className : "");
fvlinden
9

menggunakan mutasi jquery terbaru

var $target = jQuery(".required-entry");
            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    if (mutation.attributeName === "class") {
                        var attributeValue = jQuery(mutation.target).prop(mutation.attributeName);
                        if (attributeValue.indexOf("search-class") >= 0){
                            // do what you want
                        }
                    }
                });
            });
            observer.observe($target[0],  {
                attributes: true
            });

// kode apa pun yang memperbarui div yang memiliki entri wajib kelas yang dalam $ target seperti $ target.addClass ('kelas pencarian');

Hassan Ali Shahzad
sumber
Solusi yang bagus, saya menggunakannya. Namun, saya menggunakan reaksi dan salah satu masalahnya adalah saya menambahkan pengamat setiap kali elemen web dihasilkan secara virtual. Apakah ada cara untuk memeriksa apakah pengamat sudah ada?
Mikaël Mayer
Saya pikir ini telah disusutkan dan tidak boleh digunakan sesuai dengan [link] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
WebDude0482
@ WebDude0482 MutationObserver.observe tidak ditinggalkan karena merupakan "Metode Instance" dari "MutationObserver". Lihat developer.mozilla.org/en-US/docs/Web/API/MutationObserver
nu everest
8

change()tidak menyala ketika kelas CSS ditambahkan atau dihapus atau definisi berubah. Ini menyala dalam keadaan seperti ketika nilai kotak pilih dipilih atau tidak dipilih.

Saya tidak yakin apakah maksud Anda jika definisi kelas CSS diubah (yang dapat dilakukan secara terprogram tetapi membosankan dan umumnya tidak disarankan) atau jika kelas ditambahkan atau dihapus ke suatu elemen. Tidak ada cara untuk menangkap kejadian ini dengan andal dalam kedua kasus tersebut.

Anda tentu saja dapat membuat acara Anda sendiri untuk ini, tetapi ini hanya dapat digambarkan sebagai penasehat . Itu tidak akan menangkap kode yang bukan milikmu melakukannya.

Atau Anda bisa mengganti / mengganti metode addClass()(dll) di jQuery tetapi ini tidak akan menangkap ketika dilakukan melalui vanilla Javascript (walaupun saya kira Anda juga bisa mengganti metode itu).

cletus
sumber
8

Ada satu cara lagi tanpa memicu acara khusus

Plug-in jQuery untuk memantau Perubahan Elemen CSS Html oleh Rick Strahl

Mengutip dari atas

Plug-in jam tangan berfungsi dengan menghubungkan ke DOMAttrModified di FireFox, ke onPropertyChanged di Internet Explorer, atau dengan menggunakan timer dengan setInterval untuk menangani deteksi perubahan untuk browser lain. Sayangnya WebKit tidak mendukung DOMAttrModified secara konsisten saat ini sehingga Safari dan Chrome saat ini harus menggunakan mekanisme setInterval yang lebih lambat.

Amitd
sumber
6

Pertanyaan bagus. Saya menggunakan Menu Dropdown Bootstrap, dan diperlukan untuk menjalankan suatu peristiwa ketika Bootstrap Dropdown disembunyikan. Ketika dropdown dibuka, div yang berisi dengan nama kelas "tombol-grup" menambahkan kelas "terbuka"; dan tombol itu sendiri memiliki atribut "aria-extended" yang disetel ke true. Ketika dropdown ditutup, kelas "terbuka" itu dihapus dari div yang mengandung, dan aria-diperluas beralih dari true ke false.

Itu mengarahkan saya ke pertanyaan ini, tentang bagaimana mendeteksi perubahan kelas.

Dengan Bootstrap, ada "Acara Dropdown" yang dapat dideteksi. Cari "Acara Dropdown" di tautan ini. http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Berikut adalah contoh cepat-dan-kotor dari menggunakan acara ini pada Bootstrap Dropdown.

$(document).on('hidden.bs.dropdown', function(event) {
    console.log('closed');
});

Sekarang saya menyadari ini lebih spesifik daripada pertanyaan umum yang diajukan. Tapi saya membayangkan pengembang lain yang mencoba mendeteksi acara terbuka atau tertutup dengan Bootstrap Dropdown akan menemukan ini membantu. Seperti saya, mereka mungkin pada awalnya hanya mencoba mendeteksi perubahan kelas elemen (yang tampaknya tidak begitu sederhana). Terima kasih.

Ken Palmer
sumber
5

Jika Anda perlu memicu suatu peristiwa tertentu, Anda dapat mengganti metode addClass () untuk memecat acara khusus yang disebut 'classadded'.

Di sini caranya:

(function() {
    var ev = new $.Event('classadded'),
        orig = $.fn.addClass;
    $.fn.addClass = function() {
        $(this).trigger(ev, arguments);
        return orig.apply(this, arguments);
    }
})();

$('#myElement').on('classadded', function(ev, newClasses) {
    console.log(newClasses + ' added!');
    console.log(this);
    // Do stuff
    // ...
});
micred
sumber
5

Anda dapat mengikat acara DOMSubtreeModified. Saya menambahkan contoh di sini:

HTML

<div id="mutable" style="width:50px;height:50px;">sjdfhksfh<div>
<div>
  <button id="changeClass">Change Class</button>
</div>

JavaScript

$(document).ready(function() {
  $('#changeClass').click(function() {
    $('#mutable').addClass("red");
  });

  $('#mutable').bind('DOMSubtreeModified', function(e) {
      alert('class changed');
  });
});

http://jsfiddle.net/hnCxK/13/

Sativa Diva
sumber
0

jika Anda tahu acara apa yang mengubah kelas di tempat pertama Anda dapat menggunakan sedikit keterlambatan pada acara yang sama dan memeriksa kelas. contoh

//this is not the code you control
$('input').on('blur', function(){
    $(this).addClass('error');
    $(this).before("<div class='someClass'>Warning Error</div>");
});

//this is your code
$('input').on('blur', function(){
    var el= $(this);
    setTimeout(function(){
        if ($(el).hasClass('error')){ 
            $(el).removeClass('error');
            $(el).prev('.someClass').hide();
        }
    },1000);
});

http://jsfiddle.net/GuDCp/3/

Nikos Tsagkas
sumber
0
var timeout_check_change_class;

function check_change_class( selector )
{
    $(selector).each(function(index, el) {
        var data_old_class = $(el).attr('data-old-class');
        if (typeof data_old_class !== typeof undefined && data_old_class !== false) 
        {

            if( data_old_class != $(el).attr('class') )
            {
                $(el).trigger('change_class');
            }
        }

        $(el).attr('data-old-class', $(el).attr('class') );

    });

    clearTimeout( timeout_check_change_class );
    timeout_check_change_class = setTimeout(check_change_class, 10, selector);
}
check_change_class( '.breakpoint' );


$('.breakpoint').on('change_class', function(event) {
    console.log('haschange');
});
Jérome Obbiet
sumber