Auslösen von Ereignissen in CSS-Klassenänderungen in jQuery

240

Wie kann ich ein Ereignis auslösen, wenn eine CSS-Klasse mit jQuery hinzugefügt oder geändert wird? Löst das Ändern einer CSS-Klasse das jQuery- change()Ereignis aus?

user237060
quelle
8
7 Jahre später, mit der ziemlich breiten Akzeptanz von MutationObservern in modernen Browsern, sollte die hier akzeptierte Antwort wirklich aktualisiert werden, um die von Herrn Br zu sein .
Joelmdev
Dies könnte Ihnen helfen. stackoverflow.com/questions/2157963/…
PSR
Als Ergänzung zu Jasons Antwort fand ich Folgendes
Benj

Antworten:

223

Wann immer Sie eine Klasse in Ihrem Skript ändern, können Sie mit a triggerIhr eigenes Ereignis auslösen.

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

Aber ansonsten gibt es keine Möglichkeit, ein Ereignis auszulösen, wenn sich eine Klasse ändert. change()Wird erst ausgelöst, nachdem der Fokus eine Eingabe hinterlassen hat, deren Eingabe geändert wurde.

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

Weitere Informationen zu jQuery-Triggern

Jason
quelle
4
Was ist mit dem Auslösen des Ereignisses zu tun, das dann eine Funktion aufruft? Warum nicht die Funktion direkt aufrufen, anstatt sie auszulösen?
RamboNo5
43
Das Auslösen ist ein Mechanismus, mit dem bestimmte Elemente ein Ereignis "abonnieren" können. Auf diese Weise können diese Ereignisse beim Auslösen des Ereignisses alle gleichzeitig auftreten und ihre jeweilige Funktionalität ausführen. Wenn ich zum Beispiel ein Objekt habe, das sich von rot nach blau ändert, und drei Objekte darauf warten, dass es sich ändert, kann ich bei einer Änderung einfach das changeColorEreignis auslösen , und alle Objekte, die dieses Ereignis abonnieren, können entsprechend reagieren.
Jason
1
Scheint ein bisschen seltsam, dass das OP ein 'cssClassChanged'-Ereignis abhören möchte; Sicherlich wurde die Klasse als Ergebnis eines anderen 'Anwendungs'-Ereignisses wie' colourChanged 'hinzugefügt, dessen Ankündigung mit Trigger sinnvoller wäre, da ich mir nicht vorstellen kann, warum irgendetwas speziell an einer Änderung des Klassennamens interessiert wäre Ergebnis der classNameChange sicher?
Riscarrott
Wenn es speziell eine Änderung des Klassennamens wäre, die von Interesse wäre, würde ich mir vorstellen, jQuery.fn.addClass so zu dekorieren, dass 'cssClassChanged' ausgelöst wird, wie @micred sagte
riscarrott
5
@riscarrott Ich nehme nicht an, die Anwendung des OP zu kennen. Ich präsentiere ihnen nur das Konzept von Pub / Sub und sie können es anwenden, wie sie möchten. Es ist dumm zu bestreiten, wie der tatsächliche Ereignisname mit der Menge der angegebenen Informationen lauten soll.
Jason
140

IMHO ist die bessere Lösung, zwei Antworten von @ RamboNo5 und @Jason zu kombinieren

Ich meine, die addClass-Funktion zu überschreiben und ein benutzerdefiniertes Ereignis namens aufzurufen 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
quelle
Dies klingt in der Tat nach dem besten Ansatz, aber obwohl ich das Ereignis nur an "#YourExampleElementID" binde, scheint es, dass alle Klassenänderungen (dh an einem der Elemente auf meiner Seite) von diesem Handler behandelt werden ... irgendwelche Gedanken?
Filipe Correia
Es funktioniert gut für mich @FilipeCorreia. Können Sie bitte eine jsfiddle mit der Replikation Ihres Falles zur Verfügung stellen?
Arash Milani
@ Goldentoa11 Sie sollten sich an einem Abend oder Wochenende Zeit nehmen und alles überwachen, was auf einer typischen Seitenlast passiert, bei der Werbung stark genutzt wird. Sie werden begeistert sein von der Menge an Skripten, die versuchen (manchmal auf spektakulärste Weise erfolglos), solche Dinge zu tun. Ich bin auf Seiten gestoßen, die Dutzende von DOMNodeInserted / DOMSubtreeModified-Ereignissen pro Sekunde auslösen. Bis zum Beginn $()der eigentlichen Aktion sind es Hunderte von Ereignissen .
L0j1k
Vermutlich möchten Sie auch das Auslösen des gleichen Ereignisses auf removeClass
Darius am
4
Arash, ich glaube, ich verstehe, warum bei @FilipeCorreia einige Probleme mit diesem Ansatz aufgetreten sind: Wenn Sie dieses cssClassChangedEreignis an ein Element binden , müssen Sie sich darüber im Klaren sein, dass es ausgelöst wird, selbst wenn sein Kind seine Klasse wechselt. Wenn Sie dieses Ereignis beispielsweise nicht für sie auslösen möchten, fügen Sie einfach etwas wie $("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });nach dem Binden hinzu. Wie auch immer, wirklich schöne Lösung, danke!
Tonix
59

Wenn Sie Klassenänderungen erkennen möchten, verwenden Sie am besten Mutationsbeobachter, mit denen Sie die vollständige Kontrolle über alle Attributänderungen erhalten. Sie müssen den Listener jedoch selbst definieren und an das Element anhängen, das Sie gerade hören. Gut ist, dass Sie nichts manuell auslösen müssen, sobald der Listener angehängt ist.

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

});
});

In diesem Beispiel wird der Ereignis-Listener an jedes Element angehängt, aber das möchten Sie in den meisten Fällen nicht (Speicherplatz sparen). Hängen Sie diesen "attrchange" -Listener an das Element an, das Sie beobachten möchten.

Herr Br
quelle
1
Dies scheint genau das Gleiche zu tun wie die akzeptierte Antwort, jedoch mit mehr Code, der in einigen Browsern nicht unterstützt wird, und mit weniger Flexibilität. Der einzige Vorteil, den dies zu haben scheint, ist, dass es jedes Mal ausgelöst wird, wenn sich etwas auf der Seite ändert, was Ihre Leistung absolut zerstört ...
Jason
10
Ihre Aussage in der Antwort ist falsch @Json. Dies ist eigentlich der Weg, wenn Sie keinen Trigger manuell aktivieren möchten, sondern ihn automatisch aktivieren möchten. Es ist nicht nur ein Vorteil, sondern auch ein Vorteil, denn dies wurde in Frage gestellt. Zweitens war dies nur ein Beispiel, und wie im Beispiel angegeben, wird nicht empfohlen, den Ereignis-Listener an jedes Element anzuhängen, sondern nur an Elemente, die Sie abhören möchten. Daher verstehe ich nicht, warum dadurch die Leistung beeinträchtigt wird. Und als letztes ist es Zukunft, die meisten der neuesten Browser unterstützen es, caniuse.com/#feat=mutationobserver. Bitte überdenken Sie die Bearbeitung, es ist der richtige Weg.
Herr Br
1
Mit der insertionQ-Bibliothek können Sie DOM-Knoten abfangen, die angezeigt werden, wenn sie mit einem Selektor übereinstimmen. Es hat eine breitere Browserunterstützung, da es nicht verwendet wird DOMMutationObserver.
Dan Dascalescu
Dies ist eine großartige Lösung. Insbesondere zum Starten und Stoppen von Canvas- oder SVG-Animationen, wenn eine Klasse hinzugefügt oder entfernt wird. In meinem Fall, um sicherzustellen, dass sie nicht ausgeführt werden, wenn sie nicht angezeigt werden. Süss!
BBaysinger
1
Für 2019 sollte dies die akzeptierte Antwort sein. Alle gängigen Browser scheinen Mutation Observers jetzt zu unterstützen, und für diese Lösung müssen Ereignisse nicht manuell ausgelöst oder grundlegende jQuery-Funktionen neu geschrieben werden.
sp00n
53

Ich würde vorschlagen, dass Sie die Funktion addClass überschreiben. Sie können es so machen:

// 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
quelle
16
Dies mit Jasons $ (mySelector) .trigger ('cssClassChanged') zu kombinieren, wäre meiner Meinung nach der beste Ansatz.
kosoant
4
Es gibt ein Plugin, das dies generisch macht: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Jerph
37
Scheint eine großartige Möglichkeit zu sein, einen zukünftigen Betreuer zu verwirren.
Roufamatic
1
Vergessen Sie nicht, das Ergebnis der ursprünglichen Methode zurückzugeben.
Petah
1
Als Referenz verwenden Sie das Proxy-Muster en.wikipedia.org/wiki/Proxy_pattern
Darius
22

Nur ein Proof of Concept:

Schauen Sie sich das Wesentliche an, um einige Anmerkungen zu sehen und auf dem neuesten Stand zu bleiben:

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

Die Verwendung ist recht einfach. Fügen Sie einfach einen neuen Listener zu dem Knoten hinzu, den Sie beobachten möchten, und bearbeiten Sie die Klassen wie gewohnt:

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
quelle
Dies funktionierte für mich, außer dass ich mit der removeClass-Methode keine alte oder neue Klasse lesen konnte.
Slashdottir
1
@slashdottir Können Sie eine Geige erstellen und erklären , was genau nicht funktioniert .
Yckart
Ja ... funktioniert nicht. TypeError: Diese [0] ist in «return this [0] .className;» undefiniert.
Pedro Ferreira
es funktioniert aber manchmal (warum?), this[0]ist undefiniert ... ersetzen Sie einfach das Ende der Zeilen durch var oldClassund var newClassmit der folgenden Zuordnung:= (this[0] ? this[0].className : "");
fvlinden
9

unter Verwendung der neuesten jquery-Mutation

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

// jeder Code, der div mit dem erforderlichen Klasseneintrag aktualisiert, der sich in $ target befindet, wie $ target.addClass ('Suchklasse');

Hassan Ali Shahzad
quelle
Schöne Lösung, ich benutze es. Ich verwende jedoch "Reagieren" und eines der Probleme besteht darin, dass ich jedes Mal, wenn das Webelement virtuell generiert wird, einen Beobachter hinzufüge. Gibt es eine Möglichkeit zu überprüfen, ob bereits ein Beobachter anwesend ist?
Mikaël Mayer
Ich denke, dies wurde abgewertet und sollte laut [link] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
WebDude0482
@ WebDude0482 MutationObserver.observe ist nicht veraltet, da es sich um eine "Instanzmethode" des "MutationObserver" handelt. Siehe developer.mozilla.org/en-US/docs/Web/API/MutationObserver
nu everest
8

change()wird nicht ausgelöst, wenn eine CSS-Klasse hinzugefügt oder entfernt wird oder sich die Definition ändert. Es wird unter Umständen ausgelöst, wenn ein Auswahlfeldwert ausgewählt oder nicht ausgewählt ist.

Ich bin mir nicht sicher, ob Sie meinen, ob die CSS-Klassendefinition geändert wird (was programmgesteuert erfolgen kann, aber langwierig ist und nicht allgemein empfohlen wird) oder ob eine Klasse zu einem Element hinzugefügt oder daraus entfernt wird. In beiden Fällen gibt es keine Möglichkeit, dieses Ereignis zuverlässig zu erfassen.

Sie können natürlich Ihre eigene Veranstaltung dafür erstellen, dies kann jedoch nur als Beratung bezeichnet werden . Es wird kein Code erfasst, der nicht von Ihnen stammt.

Alternativ können Sie die addClass()(etc) -Methoden in jQuery überschreiben / ersetzen , dies wird jedoch nicht erfasst, wenn dies über Vanilla Javascript erfolgt (obwohl Sie diese Methoden vermutlich auch ersetzen könnten).

Cletus
quelle
8

Es gibt noch eine Möglichkeit, ohne ein benutzerdefiniertes Ereignis auszulösen

Ein jQuery-Plug-In zur Überwachung von CSS-Änderungen an HTML-Elementen von Rick Strahl

Zitat von oben

Das Watch-Plug-In funktioniert, indem Sie eine Verbindung zu DOMAttrModified in FireFox, zu onPropertyChanged in Internet Explorer herstellen oder einen Timer mit setInterval verwenden, um Änderungen für andere Browser zu erkennen. Leider unterstützt WebKit DOMAttrModified derzeit nicht konsistent, sodass Safari und Chrome derzeit den langsameren setInterval-Mechanismus verwenden müssen.

Amitd
quelle
6

Gute Frage. Ich verwende das Bootstrap-Dropdown-Menü und musste ein Ereignis ausführen, wenn ein Bootstrap-Dropdown ausgeblendet wurde. Wenn das Dropdown-Menü geöffnet wird, fügt das enthaltende div mit dem Klassennamen "button-group" eine Klasse von "open" hinzu. und die Schaltfläche selbst hat ein "aria-erweitertes" Attribut, das auf "true" gesetzt ist. Wenn das Dropdown-Menü geschlossen ist, wird diese Klasse von "open" aus dem enthaltenen div entfernt und aria-expanded von true auf false umgeschaltet.

Das führte mich zu der Frage, wie man den Klassenwechsel erkennt.

Mit Bootstrap können "Dropdown-Ereignisse" erkannt werden. Suchen Sie unter diesem Link nach "Dropdown-Ereignissen". http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Hier ist ein kurzes und schmutziges Beispiel für die Verwendung dieses Ereignisses in einem Bootstrap-Dropdown.

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

Jetzt ist mir klar, dass dies spezifischer ist als die allgemeine Frage, die gestellt wird. Ich kann mir jedoch vorstellen, dass andere Entwickler, die versuchen, ein offenes oder geschlossenes Ereignis mit einem Bootstrap-Dropdown zu erkennen, dies hilfreich finden. Wie ich gehen sie möglicherweise zunächst den Weg, einfach zu versuchen, eine Änderung der Elementklasse zu erkennen (was anscheinend nicht so einfach ist). Vielen Dank.

Ken Palmer
quelle
5

Wenn Sie ein bestimmtes Ereignis auslösen müssen, können Sie die Methode addClass () überschreiben, um ein benutzerdefiniertes Ereignis mit dem Namen 'classadded' auszulösen.

Hier wie:

(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
    // ...
});
mikred
quelle
5

Sie können das DOMSubtreeModified-Ereignis binden. Ich füge hier ein Beispiel hinzu:

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

Wenn Sie wissen, welches Ereignis die Klasse überhaupt geändert hat, können Sie eine geringfügige Verzögerung für dasselbe Ereignis verwenden und die für die Klasse überprüfen. Beispiel

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