[jquery] Firing events on CSS class changes in jQuery

How can I fire an event if a CSS class is added or changed using jQuery? Does changing of a CSS class fire the jQuery change() event?

This question is related to jquery css jquery-events

The answer is


There is one more way without triggering an custom event

A jQuery Plug-in to monitor Html Element CSS Changes by Rick Strahl

Quoting from above

The watch plug-in works by hooking up to DOMAttrModified in FireFox, to onPropertyChanged in Internet Explorer, or by using a timer with setInterval to handle the detection of changes for other browsers. Unfortunately WebKit doesn’t support DOMAttrModified consistently at the moment so Safari and Chrome currently have to use the slower setInterval mechanism.


You can bind the DOMSubtreeModified event. I add an example here:

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/


If you want to detect class change, best way is to use Mutation Observers, which gives you complete control over any attribute change. However you need to define listener yourself, and append it to element you are listening. Good thing is that you don't need to trigger anything manually once listener is appended.

$(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 this example event listener is appended to every element, but you don't want that in most cases (save memory). Append this "attrchange" listener to element you want observe.


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

If you need to trigger a specific event you can override the method addClass() to fire a custom event called 'classadded'.

Here how:

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

Good question. I'm using the Bootstrap Dropdown Menu, and needed to execute an event when a Bootstrap Dropdown was hidden. When the dropdown is opened, the containing div with a class name of "button-group" adds a class of "open"; and the button itself has an "aria-expanded" attribute set to true. When the dropdown is closed, that class of "open" is removed from the containing div, and aria-expanded is switched from true to false.

That led me to this question, of how to detect the class change.

With Bootstrap, there are "Dropdown Events" that can be detected. Look for "Dropdown Events" at this link. http://www.w3schools.com/bootstrap/bootstrap_ref_js_dropdown.asp

Here is a quick-and-dirty example of using this event on a Bootstrap Dropdown.

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

Now I realize this is more specific than the general question that's being asked. But I imagine other developers trying to detect an open or closed event with a Bootstrap Dropdown will find this helpful. Like me, they may initially go down the path of simply trying to detect an element class change (which apparently isn't so simple). Thanks.


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

// any code which update div having class required-entry which is in $target like $target.addClass('search-class');


Just a proof of concept:

Look at the gist to see some annotations and stay up-to-date:

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

The usage is quite simple, just add a new listender on the node you want to observe and manipulate the classes as usually:

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

change() does not fire when a CSS class is added or removed or the definition changes. It fires in circumstances like when a select box value is selected or unselected.

I'm not sure if you mean if the CSS class definition is changed (which can be done programmatically but is tedious and not generally recommended) or if a class is added or removed to an element. There is no way to reliably capture this happening in either case.

You could of course create your own event for this but this can only be described as advisory. It won't capture code that isn't yours doing it.

Alternatively you could override/replace the addClass() (etc) methods in jQuery but this won't capture when it's done via vanilla Javascript (although I guess you could replace those methods too).


IMHO the better solution is to combine two answers by @RamboNo5 and @Jason

I mean overridding addClass function and adding a custom event called 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
    });
});

I would suggest you override the addClass function. You can do it this way:

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

if you know a what event changed the class in the first place you may use a slight delay on the same event and the check the for the class. example

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


Examples related to jquery

How to make a variable accessible outside a function? Jquery assiging class to th in a table Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Getting all files in directory with ajax Bootstrap 4 multiselect dropdown Cross-Origin Read Blocking (CORB) bootstrap 4 file input doesn't show the file name Jquery AJAX: No 'Access-Control-Allow-Origin' header is present on the requested resource how to remove json object key and value.?

Examples related to css

need to add a class to an element Using Lato fonts in my css (@font-face) Please help me convert this script to a simple image slider Why there is this "clear" class before footer? How to set width of mat-table column in angular? Center content vertically on Vuetify bootstrap 4 file input doesn't show the file name Bootstrap 4: responsive sidebar menu to top navbar Stylesheet not loaded because of MIME-type Force flex item to span full row width

Examples related to jquery-events

Failed to load resource: the server responded with a status of 500 (Internal Server Error) in Bind function Detect Close windows event by jQuery How to bind Events on Ajax loaded Content? Get clicked element using jQuery on event? jQuery click events firing multiple times jQuery.click() vs onClick Bootstrap onClick button event Difference between $(this) and event.target? Getting the class of the element that fired an event using JQuery Attaching click event to a JQuery object not yet added to the DOM