Odpalanie zdarzeń dotyczących zmian klas CSS w jQuery

240

Jak mogę uruchomić zdarzenie, jeśli klasa CSS zostanie dodana lub zmieniona za pomocą jQuery? Czy zmiana klasy CSS wywołuje change()zdarzenie jQuery ?

użytkownik237060
źródło
8
7 lat później, wraz z dość szerokim przyjęciem MutationObservers we współczesnych przeglądarkach, zaakceptowana tutaj odpowiedź powinna zostać naprawdę zaktualizowana, tak aby należała do pana Br .
joelmdev
To może ci pomóc. stackoverflow.com/questions/2157963/…
PSR
W odpowiedzi na odpowiedź Jasona znalazłem to: stackoverflow.com/a/19401707/1579667
Benj

Odpowiedzi:

223

Za każdym razem, gdy zmieniasz klasę w skrypcie, możesz użyć a, triggeraby wywołać własne wydarzenie.

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

ale w przeciwnym razie nie, nie ma sposobu na uruchomienie zdarzenia, gdy klasa się zmieni. change()strzela tylko wtedy, gdy fokus pozostawia wejście, którego wejście zostało zmienione.

$(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>

Więcej informacji na temat wyzwalaczy jQuery

Jason
źródło
4
Jaki jest sens wyzwalania zdarzenia, które następnie wywołuje funkcję? Dlaczego nie wywołać funkcji bezpośrednio, zamiast ją uruchomić?
RamboNo5
43
Wyzwalanie to mechanizm, który pozwoli niektórym elementom „subskrybować” wydarzenie. w ten sposób, gdy zdarzenie zostanie wyzwolone, wszystkie te zdarzenia mogą się zdarzyć jednocześnie i uruchomić odpowiednią funkcjonalność. na przykład, jeśli mam jeden obiekt, który zmienia się z czerwonego na niebieski i trzy obiekty oczekujące na zmianę, kiedy się zmienia, mogę po prostu wywołać changeColorzdarzenie, a wszystkie obiekty subskrybujące to zdarzenie mogą odpowiednio zareagować.
Jason
1
Wydaje się nieco dziwne, że OP chce nasłuchiwać zdarzenia „cssClassChanged”; z pewnością klasa została dodana w wyniku jakiegoś innego zdarzenia „aplikacji”, takiego jak „colourChanged”, które lepiej byłoby ogłosić za pomocą wyzwalacza, ponieważ nie mogę sobie wyobrazić, dlaczego cokolwiek byłoby zainteresowane konkretną zmianą nazwy klasy, to bardziej wynik z classNameChange na pewno?
riscarrott
Jeśli interesująca byłaby zmiana nazwy klasy, to wyobrażam sobie dekorowanie jQuery.fn.addClass w celu wywołania „cssClassChanged” byłoby bardziej rozsądne, jak powiedział
@micred
5
@riscarrott Nie zakładam, że znam aplikację PO. Po prostu przedstawiam im koncepcję pub / sub i mogą zastosować ją w dowolny sposób. Spieranie się o rzeczywistą nazwę zdarzenia z ilością podanych informacji jest głupie.
Jason
140

IMHO lepszym rozwiązaniem jest połączenie dwóch odpowiedzi @ RamboNo5 i @Jason

Mam na myśli przesłonięcie funkcji addClass i dodanie niestandardowego zdarzenia o nazwie 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
źródło
To rzeczywiście brzmi jak najlepsze podejście, ale chociaż wiążę zdarzenie tylko z „#YourExampleElementID”, wydaje się, że wszystkie zmiany klas (tj. Na dowolnym elemencie na mojej stronie) są obsługiwane przez ten moduł obsługi ... jakieś myśli?
Filipe Correia
Działa dobrze dla mnie @FilipeCorreia. Czy możesz podać jsfiddle z replikacją swojej sprawy?
Arash Milani
@ Goldentoa11 Powinieneś poświęcić trochę czasu wieczorem lub w weekendy i monitorować wszystko, co dzieje się na typowej stronie ładującej, która intensywnie wykorzystuje reklamy. Zaskoczy Cię ilość skryptów próbujących (czasami bezskutecznie w najbardziej spektakularny sposób) robić takie rzeczy. Przeglądałem strony, które uruchamiają dziesiątki zdarzeń DOMNodeInserted / DOMSubtreeModified na sekundę, co daje w sumie setki zdarzeń, zanim dotrzesz do $()momentu rozpoczęcia prawdziwej akcji.
L0j1k
Prawdopodobnie chciałbyś również wspierać wyzwalanie tego samego zdarzenia na removeClass
Darius
4
Arash, chyba rozumiem, dlaczego @FilipeCorreia napotykało pewne problemy z tym podejściem: jeśli powiążesz to cssClassChangedzdarzenie z elementem, musisz mieć świadomość, że zostanie ono zwolnione, nawet jeśli jego dziecko zmieni klasę. Dlatego jeśli na przykład nie chcesz odpalić tego wydarzenia dla nich, po prostu dodaj coś $("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });po powiązaniu. W każdym razie naprawdę fajne rozwiązanie, dzięki!
tonix
59

Jeśli chcesz wykryć zmianę klasy, najlepszym sposobem jest użycie obserwatora mutacji, który daje ci pełną kontrolę nad każdą zmianą atrybutu. Musisz jednak samodzielnie zdefiniować słuchacza i dołączyć go do słuchanego elementu. Dobrą rzeczą jest to, że nie trzeba ręcznie uruchamiać niczego po dołączeniu detektora.

$(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
    }

});
});

W tym przykładzie detektor zdarzeń jest dołączany do każdego elementu, ale w większości przypadków nie chcesz tego (oszczędzaj pamięć). Dołącz ten detektor „attrchange” do elementu, który chcesz obserwować.

Pan Br
źródło
1
wygląda na to, że robi dokładnie to samo, co akceptowana odpowiedź, ale z większym kodem, nieobsługiwanym w niektórych przeglądarkach i mniejszą elastycznością. Jedyną korzyścią wydaje się, iż jest to, że ogień za każdym razem coś się zmienia na stronie, która będzie całkowicie zniszczyć swoje wyniki ...
Jason
10
Twoja odpowiedź w odpowiedzi jest niepoprawna @Json, jest to w rzeczywistości sposób, aby to zrobić, jeśli nie chcesz ręcznie aktywować żadnego wyzwalacza, ale aktywujesz go automatycznie. To nie tylko korzyść, ale korzyść, bo o to pytano. Po drugie, to był tylko przykład i jak mówi przykład, nie sugeruje się dołączania detektora zdarzeń do każdego elementu, ale tylko do elementów, których chcesz słuchać, więc nie rozumiem, dlaczego to zniszczy wydajność. I ostatnia rzecz, przyszłość, większość najnowszych przeglądarek obsługuje to, caniuse.com/#feat=mutationobserver. Proszę ponownie rozważyć edycję, to właściwa droga.
Pan Br
1
Biblioteka insertionQ pozwala złapać DOM węzłów wyświetlane jeśli pasują selektora. Ma szerszą obsługę przeglądarki, ponieważ nie używa DOMMutationObserver.
Dan Dascalescu
To świetne rozwiązanie. W szczególności do uruchamiania i zatrzymywania animacji na płótnie lub SVG po dodaniu lub usunięciu klasy. W moim przypadku, aby upewnić się, że nie działają po przewinięciu poza pole widzenia. Słodkie!
BBaysinger
1
Na 2019 r. Powinna to być zaakceptowana odpowiedź. Wydaje się, że wszystkie główne przeglądarki obsługują teraz Mutation Observers, a to rozwiązanie nie wymaga ręcznego uruchamiania zdarzeń ani przepisywania podstawowych funkcji jQuery.
sp00n
53

Sugeruję przesłonięcie funkcji addClass. Możesz to zrobić w ten sposób:

// 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
źródło
16
Połączenie tego z $ Jasona (mySelector) .trigger ('cssClassChanged') byłoby najlepszym podejściem, moim zdaniem.
kosoant
4
Istnieje wtyczka, która robi to ogólnie: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Jerph
37
To świetny sposób na zamieszanie przyszłego opiekuna.
roufamatic
1
Nie zapomnij zwrócić wyniku oryginalnej metody.
Petah
1
W celach informacyjnych używasz Wzorca proxy en.wikipedia.org/wiki/Proxy_pattern
Darius
22

Tylko dowód koncepcji:

Spójrz na sedno, aby zobaczyć niektóre adnotacje i być na bieżąco:

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

Użycie jest dość proste, wystarczy dodać nowego listera w węźle, który chcesz obserwować, i jak zwykle manipulować klasami:

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');
yckart
źródło
To działało dla mnie, ale nie mogłem odczytać starej lub nowej klasy za pomocą metody removeClass.
slashdottir
1
@slashdottir można utworzyć skrzypce i wyjaśnić , co dokładnie nie działa .
yckart
Tak ... nie działa. TypeError: this [0] jest niezdefiniowany w «return this [0] .className;»
Pedro Ferreira
to działa, ale czasami (dlaczego?) this[0]jest niezdefiniowane ... po prostu zamień koniec linii na var oldClassi var newClassz następującym przypisaniem:= (this[0] ? this[0].className : "");
fvlinden
9

przy użyciu najnowszej mutacji jquery

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

// każdy kod, który aktualizuje div mający wpis klasy wymagany, który jest w $ target, np. $ target.addClass ('search-class');

Hassan Ali Shahzad
źródło
Ładne rozwiązanie, używam go. Jednak używam reagowania i jednym z problemów jest to, że dodam obserwatora za każdym razem, gdy element sieciowy jest generowany wirtualnie. Czy istnieje sposób sprawdzenia, czy obserwator jest już obecny?
Mikaël Mayer,
Wydaje
@ WebDude0482 MutationObserver.observe nie jest przestarzały, ponieważ jest „metodą instancji” „MutationObserver”. Zobacz developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Everest
8

change()nie jest uruchamiany po dodaniu lub usunięciu klasy CSS lub zmianie definicji. Uruchamia się w okolicznościach, takich jak wybranie lub odznaczenie wartości pola wyboru.

Nie jestem pewien, czy masz na myśli zmianę definicji klasy CSS (co można zrobić programowo, ale jest żmudna i ogólnie nie zalecana), czy też klasa jest dodawana lub usuwana z elementu. W obu przypadkach nie ma możliwości wiarygodnego uchwycenia tego zdarzenia.

Możesz oczywiście stworzyć własne wydarzenie w tym celu, ale można to opisać jedynie jako doradztwo . Nie będzie przechwytywać kodu, który nie należy do Ciebie.

Alternatywnie możesz zastąpić / zastąpić metody addClass()(itp.) W jQuery, ale to nie zostanie przechwycone, gdy zostanie to zrobione za pomocą waniliowego Javascript (chociaż myślę, że możesz również zastąpić te metody).

Cletus
źródło
8

Jest jeszcze jeden sposób bez wyzwalania niestandardowego zdarzenia

Wtyczka jQuery do monitorowania zmian HTML CSS elementu HTML przez Rick Strahl

Cytowanie z góry

Wtyczka zegarka działa poprzez podłączenie do DOMAttrModified w FireFox, onPropertyChanged w Internet Explorer lub za pomocą timera z setInterval do obsługi wykrywania zmian w innych przeglądarkach. Niestety WebKit nie obsługuje obecnie DOMAttrModified, więc Safari i Chrome muszą obecnie korzystać z wolniejszego mechanizmu setInterval.

Amitd
źródło
6

Dobre pytanie. Korzystam z menu rozwijanego Bootstrap i potrzebowałem wykonać zdarzenie, gdy menu rozwijane Bootstrap było ukryte. Po otwarciu menu zawierającego div z nazwą klasy „grupa przycisków” dodaje klasę „otwarta”; a sam przycisk ma atrybut „aria-expanded” ustawiony na true. Kiedy menu rozwijane jest zamknięte, klasa „otwarta” jest usuwana z zawierającego div, a aria-expanded jest zmieniana z true na false.

Doprowadziło mnie to do pytania, jak wykryć zmianę klasy.

Dzięki Bootstrap można wykryć „Zdarzenia rozwijane”. Znajdź „Zdarzenia rozwijane” pod tym linkiem. http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Oto szybki i brudny przykład użycia tego zdarzenia w menu rozwijanym Bootstrap.

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

Teraz zdaję sobie sprawę, że jest to bardziej szczegółowe niż zadawane ogólne pytanie. Ale wyobrażam sobie, że inni programiści próbujący wykryć otwarte lub zamknięte zdarzenie za pomocą menu rozwijanego Bootstrap uznają to za pomocne. Podobnie jak ja, początkowo mogą pójść ścieżką po prostu próbując wykryć zmianę klasy elementu (co najwyraźniej nie jest takie proste). Dzięki.

Ken Palmer
źródło
5

Jeśli chcesz wyzwolić określone zdarzenie, możesz przesłonić metodę addClass (), aby uruchomić niestandardowe zdarzenie o nazwie „classadded”.

Oto jak:

(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
źródło
5

Możesz powiązać zdarzenie DOMSubtreeModified. Tutaj dodaję przykład:

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
źródło
0

jeśli wiesz, które wydarzenie zmieniło klasę, możesz użyć niewielkiego opóźnienia na tym samym wydarzeniu i sprawdzić, czy klasa. przykład

//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
źródło
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
źródło