Déclencher des événements sur les changements de classe CSS dans jQuery

240

Comment puis-je déclencher un événement si une classe CSS est ajoutée ou modifiée à l'aide de jQuery? Le changement d'une classe CSS déclenche-t-il l' change()événement jQuery ?

user237060
la source
8
7 ans plus tard, avec l'adoption assez large de MutationObservers dans les navigateurs modernes, la réponse acceptée ici devrait vraiment être mise à jour pour être celle de M. Br's .
joelmdev
Cela pourrait vous aider. stackoverflow.com/questions/2157963/…
PSR
En réponse à la réponse de Jason, j'ai trouvé ceci: stackoverflow.com/a/19401707/1579667
Benj

Réponses:

223

Chaque fois que vous changez une classe dans votre script, vous pouvez utiliser un triggerpour déclencher votre propre événement.

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

mais sinon, il n'y a aucun moyen de déclencher un événement quand une classe change. change()ne se déclenche que lorsque le focus laisse une entrée dont l'entrée a été modifiée.

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

Plus d'informations sur les déclencheurs jQuery

Jason
la source
4
Quel est le problème avec le déclenchement de l'événement qui appelle ensuite une fonction? Pourquoi ne pas appeler la fonction directement, au lieu de la déclencher?
RamboNo5
43
Le déclenchement est un mécanisme qui permettra à certains éléments de «s'abonner» à un événement. de cette façon, lorsque l'événement est déclenché, ces événements peuvent tous se produire en même temps et exécuter leurs fonctionnalités respectives. par exemple, si j'ai un objet qui passe du rouge au bleu et trois objets en attente de changement, quand il change, je peux simplement déclencher l' changeColorévénement, et tous ces objets abonnés à cet événement peuvent réagir en conséquence.
Jason
1
Semble un peu étrange que l'OP veuille écouter un événement 'cssClassChanged'; la classe a sûrement été ajoutée à la suite d'un autre événement "application" tel que "colourChanged" qui aurait plus de sens à annoncer avec déclencheur car je ne peux pas vraiment imaginer pourquoi quelque chose serait spécifiquement intéressé par un changement de nom de classe, c'est plus le résultat du classNameChange sûrement?
riscarrott
Si c'était spécifiquement un changement de nom de classe qui était intéressant, j'imagine que décorer jQuery.fn.addClass pour déclencher 'cssClassChanged' serait plus sensé comme @micred l'a dit
riscarrott
5
@riscarrott je ne présume pas connaître l'application du PO. Je leur présente simplement le concept de pub / sub et ils peuvent l'appliquer comme ils le souhaitent. Il est stupide de contester le nom réel de l'événement avec la quantité d'informations fournies.
Jason
140

À mon humble avis, la meilleure solution consiste à combiner deux réponses de @ RamboNo5 et @Jason

Je veux dire remplacer la fonction addClass et ajouter un événement personnalisé appelé 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
la source
Cela ressemble en effet à la meilleure approche, mais bien que je ne lie l'événement qu'à "#YourExampleElementID", il semble que tous les changements de classe (c'est-à-dire sur l'un des éléments de ma page) sont gérés par ce gestionnaire ... des réflexions?
Filipe Correia
Cela fonctionne bien pour moi @FilipeCorreia. Pouvez-vous s'il vous plaît fournir un jsfiddle avec la réplication de votre cas?
Arash Milani
@ Goldentoa11 Vous devriez prendre le temps un soir ou un week-end et surveiller tout ce qui se passe sur un chargement de page typique qui utilise beaucoup la publicité. Vous serez époustouflé par le volume de scripts tentant (parfois sans succès de la manière la plus spectaculaire) de faire des choses comme ça. J'ai parcouru des pages qui déclenchent des dizaines d'événements DOMNodeInsert / DOMSubtreeModified par seconde, totalisant des centaines d'événements au moment où vous y accédez $(), lorsque l'action réelle commence.
L0j1k
Vous voudriez probablement aussi prendre en charge le déclenchement du même événement sur removeClass
Darius
4
Arash, je suppose que je comprends pourquoi @FilipeCorreia rencontrait des problèmes avec cette approche: si vous liez cet cssClassChangedévénement à un élément, vous devez être conscient qu'il sera déclenché même lorsque son enfant changera de classe. Par conséquent, si, par exemple, vous ne souhaitez pas déclencher cet événement pour eux, ajoutez simplement quelque chose comme $("#YourExampleElementID *").on('cssClassChanged', function(e) { e.stopPropagation(); });après votre liaison. Quoi qu'il en soit, solution vraiment sympa, merci!
tonix
59

Si vous souhaitez détecter un changement de classe, la meilleure façon est d'utiliser les observateurs de mutation, ce qui vous donne un contrôle complet sur tout changement d'attribut. Cependant, vous devez définir l'auditeur vous-même et l'ajouter à l'élément que vous écoutez. Heureusement, vous n'avez pas besoin de déclencher quoi que ce soit manuellement une fois l'écouteur ajouté.

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

});
});

Dans cet exemple, l'écouteur d'événements est ajouté à chaque élément, mais vous ne le souhaitez pas dans la plupart des cas (économiser de la mémoire). Ajoutez cet écouteur "attrchange" à l'élément que vous souhaitez observer.

Mr Br
la source
1
cela semble faire exactement la même chose que la réponse acceptée, mais avec plus de code, non pris en charge sur certains navigateurs et avec moins de flexibilité. le seul avantage que cela semble avoir, c'est qu'il se déclenchera chaque fois que quelque chose change sur la page, ce qui détruira absolument vos performances ...
Jason
10
Votre déclaration en réponse est incorrecte @Json, c'est en fait la façon de le faire si vous ne souhaitez activer aucun déclencheur manuellement, mais l'activer automatiquement. Ce n'est pas seulement un avantage, c'est l'avantage, car cela a été posé en question. Deuxièmement, ce n'était qu'un exemple, et comme il est dit dans l'exemple, il n'est pas suggéré d'ajouter l'écouteur d'événements à chaque élément, mais uniquement aux éléments que vous souhaitez écouter, donc je ne comprends pas pourquoi cela détruira les performances. Et la dernière chose, c'est l'avenir, la plupart des derniers navigateurs le prennent en charge, caniuse.com/#feat=mutationobserver . Veuillez reconsidérer la modification, c'est la bonne façon.
Mr Br
1
La bibliothèque insertionQ vous permet d'attraper les nœuds DOM qui apparaissent s'ils correspondent à un sélecteur. Il a un support de navigateur plus large car il n'utilise pas DOMMutationObserver.
Dan Dascalescu
C'est une excellente solution. En particulier pour démarrer et arrêter des animations canvas ou SVG lorsqu'une classe est ajoutée ou supprimée. Dans mon cas, pour m'assurer qu'ils ne fonctionnent pas lorsqu'ils sont hors de vue. Doux!
BBaysinger
1
Pour 2019, cela devrait être la réponse acceptée. Tous les principaux navigateurs semblent désormais prendre en charge les observateurs de mutation, et cette solution ne nécessite pas de déclencher les événements manuellement ou de réécrire les fonctionnalités de base de jQuery.
sp00n
53

Je vous suggère de remplacer la fonction addClass. Vous pouvez le faire de cette façon:

// 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
la source
16
Combiner cela avec $ (mySelector) .trigger ('cssClassChanged') de Jason serait la meilleure approche, je pense.
kosoant
4
Il y a un plugin qui fait cela de manière générique: github.com/aheckmann/jquery.hook/blob/master/jquery.hook.js
Jerph
37
Semble être un excellent moyen de confondre un futur mainteneur.
roufamatic
1
N'oubliez pas de renvoyer le résultat de la méthode d'origine.
Petah
1
Pour référence, vous utilisez le modèle de proxy en.wikipedia.org/wiki/Proxy_pattern
Darius
22

Juste une preuve de concept:

Regardez l'essentiel pour voir quelques annotations et restez à jour:

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'utilisation est assez simple, il suffit d'ajouter un nouveau listender sur le nœud que vous souhaitez observer et manipuler les classes comme d'habitude:

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
la source
Cela a fonctionné pour moi, sauf que je ne pouvais pas lire l'ancienne ou la nouvelle classe avec la méthode removeClass.
slashdottir
1
@slashdottir Pouvez-vous créer un violon et expliquer ce qui ne fonctionne pas exactement .
yckart
Ouais ... ça ne marche pas. TypeError: this [0] n'est pas défini dans «return this [0] .className;»
Pedro Ferreira
cela fonctionne mais parfois (pourquoi?), this[0]n'est pas défini ... remplacez simplement la fin des lignes par var oldClasset var newClassavec l'affectation suivante:= (this[0] ? this[0].className : "");
fvlinden
9

en utilisant la dernière mutation 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
            });

// tout code qui met à jour div ayant une entrée obligatoire de classe qui est dans $ target comme $ target.addClass ('search-class');

Hassan Ali Shahzad
la source
Belle solution, je l'utilise. Cependant, j'utilise react et l'un des problèmes est que j'ajoute un observateur à chaque fois que l'élément web est virtuellement généré. Existe-t-il un moyen de vérifier si un observateur est déjà présent?
Mikaël Mayer
Je pense que cela a été déprécié et ne devrait pas être utilisé selon [link] developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
WebDude0482
@ WebDude0482 MutationObserver.observe n'est pas déconseillé car il s'agit d'une "méthode d'instance" du "MutationObserver". Voir developer.mozilla.org/en-US/docs/Web/API/MutationObserver
nu everest
8

change()ne se déclenche pas lorsqu'une classe CSS est ajoutée ou supprimée ou que la définition change. Il se déclenche dans des circonstances comme lorsqu'une valeur de zone de sélection est sélectionnée ou désélectionnée.

Je ne sais pas si vous voulez dire si la définition de classe CSS est modifiée (ce qui peut être fait par programme mais est fastidieux et n'est généralement pas recommandé) ou si une classe est ajoutée ou supprimée à un élément. Il n'y a aucun moyen de capturer de manière fiable ce qui se passe dans les deux cas.

Vous pouvez bien sûr créer votre propre événement pour cela, mais cela ne peut être décrit que comme un conseil . Il ne capturera pas de code qui ne vous appartient pas.

Alternativement, vous pouvez remplacer / remplacer les addClass()méthodes (etc) dans jQuery mais cela ne sera pas capturé quand cela sera fait via vanilla Javascript (bien que je suppose que vous pourriez également remplacer ces méthodes).

cletus
la source
8

Il y a un autre moyen sans déclencher un événement personnalisé

Un plug-in jQuery pour surveiller les modifications CSS des éléments HTML par Rick Strahl

Citant d'en haut

Le plug-in de surveillance fonctionne en se connectant à DOMAttrModified dans FireFox, à onPropertyChanged dans Internet Explorer, ou en utilisant un minuteur avec setInterval pour gérer la détection des modifications pour d'autres navigateurs. Malheureusement, WebKit ne prend pas en charge DOMAttrModified de manière cohérente pour le moment, donc Safari et Chrome doivent actuellement utiliser le mécanisme setInterval plus lent.

Amitd
la source
6

Bonne question. J'utilise le menu déroulant Bootstrap et j'avais besoin d'exécuter un événement lorsqu'un Bootstrap Dropdown était masqué. Lorsque la liste déroulante est ouverte, le div contenant avec un nom de classe de "bouton-groupe" ajoute une classe de "open"; et le bouton lui-même a un attribut "aria-expand" défini sur true. Lorsque la liste déroulante est fermée, cette classe "open" est supprimée de la div contenant, et aria-expand est passé de true à false.

Cela m'a amené à cette question, de savoir comment détecter le changement de classe.

Avec Bootstrap, il existe des "événements de liste déroulante" qui peuvent être détectés. Recherchez "Dropdown Events" sur ce lien. http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Voici un exemple rapide et sale d'utilisation de cet événement sur une liste déroulante Bootstrap.

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

Maintenant, je me rends compte que c'est plus spécifique que la question générale qui est posée. Mais j'imagine que d'autres développeurs essayant de détecter un événement ouvert ou fermé avec une liste déroulante Bootstrap trouveront cela utile. Comme moi, ils peuvent initialement tenter de détecter un changement de classe d'élément (ce qui n'est apparemment pas si simple). Merci.

Ken Palmer
la source
5

Si vous devez déclencher un événement spécifique, vous pouvez remplacer la méthode addClass () pour déclencher un événement personnalisé appelé «classadded».

Voici comment:

(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
la source
5

Vous pouvez lier l'événement DOMSubtreeModified. J'ajoute un exemple ici:

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
la source
0

si vous connaissez un événement qui a changé la classe en premier lieu, vous pouvez utiliser un léger retard sur le même événement et vérifier la classe. exemple

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