Attivazione di eventi sulle modifiche alla classe CSS in jQuery


240

Come posso generare un evento se una classe CSS viene aggiunta o modificata usando jQuery? Il cambio di una classe CSS genera l' change()evento jQuery ?


8
7 anni dopo, con l'adozione abbastanza ampia di MutationObservers nei browser moderni, la risposta accettata qui dovrebbe essere davvero aggiornata per essere quella di Mr Br .
Joelmdev,

Questo potrebbe aiutarti. stackoverflow.com/questions/2157963/…
PSR

In completamento alla risposta di Jason, ho trovato questo: stackoverflow.com/a/19401707/1579667
Benj

Risposte:


223

Ogni volta che cambi una classe nella tua sceneggiatura, puoi usare a triggerper creare il tuo evento.

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

ma in caso contrario, no, non esiste un modo predefinito per generare un evento quando una classe cambia. change()si attiva solo dopo che il focus lascia un input il cui input è stato modificato.

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

Maggiori informazioni sui trigger di jQuery


4
Qual è il problema con l'attivazione dell'evento che quindi chiama una funzione? Perché non chiamare direttamente la funzione, invece di attivarla?
Rambo No5,

43
Il trigger è un meccanismo che consentirà a determinati elementi di "iscriversi" a un evento. in questo modo, quando l'evento viene attivato, tali eventi possono accadere contemporaneamente ed eseguire le rispettive funzionalità. per esempio, se ho un oggetto che cambia da rosso a blu e tre oggetti in attesa che cambi, quando cambia, posso semplicemente attivare l' changeColorevento e tutti quegli oggetti che si iscrivono a quell'evento possono reagire di conseguenza.
Jason il

1
Sembra un po 'strano che l'OP voglia ascoltare un evento' cssClassChanged '; sicuramente la classe è stata aggiunta come risultato di qualche altro evento 'application' come 'colourChanged' che avrebbe più senso annunciare con trigger dato che non riesco proprio a immaginare perché qualcosa possa essere interessato in particolare a un cambio di className, è più il risultato del classNameChange sicuramente?
riscarrott

Se fosse specificamente un cambio di className a interessare, allora immaginerei che decorare jQuery.fn.addClass per attivare 'cssClassChanged' sarebbe più sensato come ha detto
@micred

5
@riscarrott non presumo di conoscere l'applicazione del PO. Sto solo presentando loro il concetto di pub / sub e possono applicarlo come vorrebbero. Discutere quale dovrebbe essere il nome dell'evento effettivo con la quantità di informazioni fornite è sciocco.
Jason

140

IMHO la soluzione migliore è combinare due risposte di @ RamboNo5 e @Jason

Intendo sostituire la funzione addClass e aggiungere un evento personalizzato chiamato 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
    });
});

Questo in effetti sembra l'approccio migliore, ma sebbene io associ l'evento solo a "#YourExampleElementID", sembra che tutti i cambi di classe (cioè, su uno qualsiasi degli elementi della mia pagina) siano gestiti da questo gestore ... qualche pensiero?
Filipe Correia

Funziona bene per me @FilipeCorreia. Potete per favore fornire un jsfiddle con la replica del vostro caso?
Arash Milani

@ Goldentoa11 Dovresti prenderti del tempo qualche sera o un fine settimana e monitorare tutto ciò che accade su un tipico pageload che utilizza pesantemente la pubblicità. Sarai sorpreso dal volume di script che tentano (a volte senza successo nel modo più spettacolare) di fare cose del genere. Mi sono imbattuto in pagine che generano decine di eventi DOMNodeInserted / DOMSubtreeModified al secondo, per un totale di centinaia di eventi quando si arriva a $(), quando inizia la vera azione.
L0j1k,

Presumibilmente vorresti anche supportare l'attivazione dello stesso evento su removeClass
Darius

4
Arash, immagino di capire perché @FilipeCorreia stava riscontrando alcuni problemi con questo approccio: se associ questo cssClassChangedevento a un elemento, devi essere consapevole che verrà generato anche quando suo figlio cambierà classe. Pertanto, se per esempio non vuoi lanciare questo evento per loro, aggiungi qualcosa come $("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });dopo il tuo bind. Comunque, davvero una bella soluzione, grazie!
tonix,

59

Se vuoi rilevare il cambio di classe, il modo migliore è usare gli osservatori di mutazioni, che ti danno il controllo completo su ogni cambiamento di attributo. Tuttavia, devi definire tu stesso l'ascoltatore e aggiungerlo all'elemento che stai ascoltando. La cosa buona è che non è necessario attivare nulla manualmente una volta aggiunto l'ascoltatore.

$(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 questo esempio il listener di eventi viene aggiunto a ogni elemento, ma nella maggior parte dei casi non lo si desidera (salvare memoria). Aggiungi questo ascoltatore "attrchange" all'elemento che vuoi osservare.


1
sembra che stia facendo esattamente la stessa cosa che sta facendo la risposta accettata, ma con più codice, non supportato su alcuni browser e con meno flessibilità. l'unico vantaggio che questo sembra avere è che sparerà ogni volta che qualcosa cambia nella pagina, il che distruggerà assolutamente la tua esibizione ...
Jason,

10
La tua affermazione in risposta è errata @Json, questo è in realtà il modo di farlo se non vuoi attivare alcun trigger manualmente, ma averlo attivo automaticamente. Non è solo un vantaggio, è un vantaggio, perché questo è stato posto in questione. In secondo luogo, questo era solo un esempio e, come si dice nell'esempio, non è consigliabile aggiungere l'ascoltatore di eventi a tutti gli elementi ma solo agli elementi che si desidera ascoltare, quindi non capisco perché distruggerà le prestazioni. E l'ultima cosa, è il futuro, la maggior parte degli ultimi browser lo supporta, caniuse.com/#feat=mutationobserver . Per favore, riconsidera la modifica, è la strada giusta.
Mr Br,

1
La libreria insertionQ consente di rilevare i nodi DOM visualizzati se corrispondono a un selettore. Ha un supporto browser più ampio perché non lo utilizza DOMMutationObserver.
Dan Dascalescu,

Questa è un'ottima soluzione In particolare per l'avvio e l'interruzione di animazioni di canvas o SVG quando una classe viene aggiunta o rimossa. Nel mio caso per essere sicuro che non siano in esecuzione quando sono sfuggiti alla vista. Dolce!
BBaysinger,

1
Per il 2019, questa dovrebbe essere la risposta accettata. Tutti i principali browser sembrano supportare ora gli osservatori di mutazioni e questa soluzione non richiede di attivare manualmente eventi o riscrivere le funzionalità jQuery di base.
sp00n,

53

Vorrei suggerire di ignorare la funzione addClass. Puoi farlo in questo modo:

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

16
Combinarlo con $ (mySelector) .trigger di Jason ('cssClassChanged') sarebbe l'approccio migliore che penso.
kosoant,

4
C'è un plugin che lo fa genericamente: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Jerph

37
Sembra un ottimo modo per confondere un futuro manutentore.
roufamatic,

1
Non dimenticare di restituire il risultato del metodo originale.
Petah,

1
Come riferimento, stai usando il modello proxy en.wikipedia.org/wiki/Proxy_pattern
Darius

22

Solo una prova di concetto:

Guarda l'essenza per vedere alcune annotazioni e rimanere aggiornato:

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

L'utilizzo è abbastanza semplice, basta aggiungere un nuovo listener sul nodo che si desidera osservare e manipolare le classi come al solito:

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

Questo ha funzionato per me, tranne per il fatto che non potevo leggere la vecchia o la nuova classe con il metodo removeClass.
slashdottir,

1
@slashdottir Puoi creare un violino e spiegare cosa non funziona esattamente .
yckart,

Sì ... non funziona. TypeError: this [0] non è definito in «return this [0] .className;»
Pedro Ferreira,

funziona ma a volte (perché?), this[0]non è definito ... è sufficiente sostituire la fine delle righe con var oldClasse var newClasscon il seguente incarico:= (this[0] ? this[0].className : "");
fvlinden

9

usando l'ultima mutazione 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
            });

// qualsiasi codice che aggiorna div con classe richiesto-entry che è in $ target come $ target.addClass ('classe di ricerca');


Bella soluzione, la sto usando. Tuttavia, sto usando la reazione e uno dei problemi è che aggiungo un osservatore ogni volta che l'elemento web viene generato virtualmente. C'è un modo per verificare se è già presente un osservatore?
Mikaël Mayer,

Penso che questo sia stato deprezzato e non dovrebbe essere usato secondo [link] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
WebDude0482

@ WebDude0482 MutationObserver.observe non è obsoleto in quanto è un "Metodo di istanza" di "MutationObserver". Vedi developer.mozilla.org/en-US/docs/Web/API/MutationObserver
nu everest

8

change()non si attiva quando una classe CSS viene aggiunta o rimossa o la definizione cambia. Viene generato in circostanze come quando un valore di casella di selezione viene selezionato o non selezionato.

Non sono sicuro se intendi se la definizione della classe CSS è cambiata (cosa che può essere fatta a livello di programmazione ma è noiosa e generalmente sconsigliata) o se una classe viene aggiunta o rimossa a un elemento. Non è possibile catturare in modo affidabile ciò che accade in entrambi i casi.

Ovviamente potresti creare il tuo evento per questo, ma questo può essere descritto solo come consulenza . Non acquisirà il codice che non è il tuo a farlo.

In alternativa, potresti sostituire / sostituire i addClass()metodi (ecc.) In jQuery ma questo non verrà catturato quando viene eseguito tramite Javascript vanilla (anche se immagino che potresti sostituire anche questi metodi).


8

C'è un altro modo senza innescare un evento personalizzato

Un plug-in jQuery per monitorare le modifiche CSS dell'elemento HTML di Rick Strahl

Citando dall'alto

Il plug-in watch funziona collegandosi a DOMAttrModified in FireFox, a onPropertyChanged in Internet Explorer o utilizzando un timer con setInterval per gestire il rilevamento di modifiche per altri browser. Sfortunatamente WebKit al momento non supporta costantemente DOMAttrModified, quindi Safari e Chrome devono attualmente utilizzare il meccanismo setInterval più lento.


6

Buona domanda. Sto usando il menu a discesa Bootstrap e avevo bisogno di eseguire un evento quando un menu a discesa Bootstrap era nascosto. Quando viene aperto il menu a discesa, il div contenente un nome di classe di "gruppo-pulsante" aggiunge una classe di "aperto"; e il pulsante stesso ha un attributo "aria-espanso" impostato su true. Quando il menu a discesa viene chiuso, quella classe di "open" viene rimossa dal div contenente e l'aria-espanso passa da vero a falso.

Ciò mi ha portato a questa domanda, su come rilevare il cambio di classe.

Con Bootstrap, ci sono "Eventi a discesa" che possono essere rilevati. Cerca "Eventi a discesa" a questo link. http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Ecco un esempio veloce e sporco dell'utilizzo di questo evento in un menu a discesa Bootstrap.

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

Ora mi rendo conto che questo è più specifico della domanda generale che viene posta. Ma immagino che altri sviluppatori che provano a rilevare un evento aperto o chiuso con un menu a discesa Bootstrap lo trovino utile. Come me, inizialmente potrebbero seguire la strada del semplice tentativo di rilevare un cambio di classe di elementi (che apparentemente non è così semplice). Grazie.


5

Se devi attivare un evento specifico, puoi ignorare il metodo addClass () per generare un evento personalizzato chiamato 'classadded'.

Ecco come:

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

5

È possibile associare l'evento DOMSubtreeModified. Aggiungo un esempio qui:

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/


0

se si conosce un evento quale ha cambiato la classe in primo luogo, è possibile utilizzare un leggero ritardo sullo stesso evento e controllare la per la classe. esempio

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


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');
});
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.