254

Is it possible to create an event listener in jQuery that can be bound to any style changes? For example, if I want to "do" something when an element changes dimensions, or any other changes in the style attribute I could do:

$('div').bind('style', function() {
    console.log($(this).css('height'));
});

$('div').height(100); // yields '100'

It would be really useful.

Any ideas?

UPDATE

Sorry for answering this myself, but I wrote a neat solution that might fit someone else:

(function() {
    var ev = new $.Event('style'),
        orig = $.fn.css;
    $.fn.css = function() {
        $(this).trigger(ev);
        return orig.apply(this, arguments);
    }
})();

This will temporary override the internal prototype.css method and the redefine it with a trigger at the end. So it works like this:

$('p').bind('style', function(e) {
    console.log( $(this).attr('style') );
});

$('p').width(100);
$('p').css('color','red');
jcalais
  • 63
  • 1
  • 4
David Hellsing
  • 106,495
  • 44
  • 176
  • 212
  • yup, nice solution, but in a real application, i suspect this might take a lot of resources, and even make the browser seem slow to keep up with the user's interactions. I'd love to know if that is the case or not. – pixeline Jan 28 '10 at 23:07
  • 1
    @pixeline: I can't see that it should be that much slower. jQuery still calls the css prototype anyway, I'm just adding a trigger to it. So it's basically just one extra method call. – David Hellsing Jan 29 '10 at 09:18
  • I understand that: it just seems that in the flow of a user interacting with an application, css() is called a lot of time. Depending on what you do with that trigger (if it's just a console log, i guess you are safe of course), this might create issues. I'm just wondering. It depends on the application too. I personally tend to do a lot of visual feedback in my stuff. – pixeline Jan 29 '10 at 10:07
  • Theres a couple more things to do to make it work with existing jquery css calls: 1) you would want to return the element out of your new css function or else it will break fluent style css calls. eg. $('p').css('display', 'block').css('color','black'); etc. 2) if it is a call for a property value (eg. 'obj.css('height');') you would want to return the return value of the original function - I do this by checking if the arguments list for the function only contains one argument. – theringostarrs Jun 12 '12 at 03:51
  • 1
    When you answer your own question, the answer should be posted as an answer, not an update to the question itself. – theMayer Aug 19 '14 at 13:00
  • @techfoobar's link is broken. You can see the plugin's Github repository here: https://github.com/techfoobar/jquery-style-listener. – Mikayla Maki Sep 15 '15 at 19:57
  • @TrentonMaki - Yep, my domain expired!! Thank you for adding the github link. :) – techfoobar Sep 16 '15 at 12:10
  • 1
    I made a more robust version of this approach that also works when the _style attribute itself_ is being removed. It will throw a 'style' event for each style that is being removed this way. https://gist.github.com/bbottema/426810c21ae6174148d4 – Benny Bottema Oct 18 '15 at 16:16

11 Answers11

371

Things have moved on a bit since the question was asked - it is now possible to use a MutationObserver to detect changes in the 'style' attribute of an element, no jQuery required:

var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutationRecord) {
        console.log('style changed!');
    });    
});

var target = document.getElementById('myId');
observer.observe(target, { attributes : true, attributeFilter : ['style'] });

The argument that gets passed to the callback function is a MutationRecord object that lets you get hold of the old and new style values.

Support is good in modern browsers including IE 11+.

codebox
  • 19,927
  • 9
  • 63
  • 81
  • 45
    Keep in mind that this only works with inline styles, not when the style changes as a consequence of class change or `@media` change. – fregante Mar 25 '16 at 13:10
  • 3
    @bfred.it would you have an idea of how to achieve just that? I'm wanting to run some js after a class's css rules have been applied to an element. – Kragalon Apr 18 '16 at 07:03
  • 1
    You could use `getComputedStyle` with `setInterval` but that would slow down your website a lot. – fregante Apr 18 '16 at 08:01
  • Note: MutationObserver does not actually work in all versions of Android 4.4, despite what caniuse says. – James G. May 11 '16 at 22:46
  • See this twiddle for an example of changing the background color of a textarea to blue when the width of the textarea is stretched past 300px. https://jsfiddle.net/RyanNerd/y2q8wuf0/ (unfortunately there appears to be a bug in FF that hangs the script at `HTMLElement.style.prop = "value"` – RyanNerd Aug 31 '17 at 08:10
  • This is helpful to see when / if an element has been changed, but I can't see _why_ it was changed - meaning what was the javascript that made the mutation? Does this method provide that information? – elPastor Sep 06 '18 at 09:54
  • It works for modern browsers, at least chrome, thanks! – Sunding Wei Aug 23 '22 at 08:31
22

Since jQuery is open-source, I would guess that you could tweak the css function to call a function of your choice every time it is invoked (passing the jQuery object). Of course, you'll want to scour the jQuery code to make sure there is nothing else it uses internally to set CSS properties. Ideally, you'd want to write a separate plugin for jQuery so that it does not interfere with the jQuery library itself, but you'll have to decide whether or not that is feasible for your project.

Josh Stodola
  • 81,538
  • 47
  • 180
  • 227
20

The declaration of your event object has to be inside your new css function. Otherwise the event can only be fired once.

(function() {
    orig = $.fn.css;
    $.fn.css = function() {
        var ev = new $.Event('style');
        orig.apply(this, arguments);
        $(this).trigger(ev);
    }
})();
thSoft
  • 21,755
  • 5
  • 88
  • 103
Mike Allen
  • 217
  • 2
  • 2
  • Great, thank you. Some people tell change the original source but your extension is the best. I had some problems but finally it works. – ccsakuweb Jul 03 '12 at 08:23
  • It continuously firing, after executing this code, and not even achieve the require result. – KumailR Mar 28 '19 at 15:23
16

I think the best answer if from Mike in the case you can't launch your event because is not from your code. But I get some errors when I used it. So I write a new answer for show you the code that I use.

Extension

// Extends functionality of ".css()"
// This could be renamed if you'd like (i.e. "$.fn.cssWithListener = func ...")
(function() {
    orig = $.fn.css;
    $.fn.css = function() {
        var result = orig.apply(this, arguments);
        $(this).trigger('stylechanged');
        return result;
    }
})();

Usage

// Add listener
$('element').on('stylechanged', function () {
    console.log('css changed');
});

// Perform change
$('element').css('background', 'red');

I got error because var ev = new $.Event('style'); Something like style was not defined in HtmlDiv.. I removed it, and I launch now $(this).trigger("stylechanged"). Another problem was that Mike didn't return the resulto of $(css, ..) then It can make problems in some cases. So I get the result and return it. Now works ^^ In every css change include from some libs that I can't modify and trigger an event.

Michael
  • 9,639
  • 3
  • 64
  • 69
ccsakuweb
  • 789
  • 5
  • 17
  • Very helpful and clever – dgo Mar 19 '18 at 18:24
  • Like mentioned in this answer, a means would be helpful if the event ISN'T generated from your code. Just be aware that this only targets changes via the css() function in jQuery. If the outside code doesn't make the changes vie the css function, the event handler never gets called. – yougotiger Mar 24 '21 at 00:10
5

As others have suggested, if you have control over whatever code is changing the style of the element you could fire a custom event when you change the element's height:

$('#blah').bind('height-changed',function(){...});
...
$('#blah').css({height:'100px'});
$('#blah').trigger('height-changed');

Otherwise, although pretty resource-intensive, you could set a timer to periodically check for changes to the element's height...

droo
  • 1,059
  • 6
  • 5
2

Interesting question. The problem is that height() does not accept a callback, so you wouldn't be able to fire up a callback. Use either animate() or css() to set the height and then trigger the custom event in the callback. Here is an example using animate() , tested and works (demo), as a proof of concept :

$('#test').bind('style', function() {
    alert($(this).css('height'));
});

$('#test').animate({height: 100},function(){
$(this).trigger('style');
}); 
pixeline
  • 17,669
  • 12
  • 84
  • 109
  • 9
    Sorry, but what did this prove? You `trigger()` the event manually. I would think the OP is after some auto-triggered event when a style attribute is modified. – JPot Jan 28 '10 at 21:21
  • @JPot he is showing that the height attribute does not throw events when changed. – AnthonyJClink Feb 23 '16 at 22:31
2

There is no inbuilt support for the style change event in jQuery or in java script. But jQuery supports to create custom event and listen to it but every time there is a change, you should have a way to trigger it on yourself. So it will not be a complete solution.

Teja Kantamneni
  • 17,402
  • 12
  • 56
  • 86
2

you can try Jquery plugin , it trigger events when css is change and its easy to use

http://meetselva.github.io/#gist-section-attrchangeExtension
 $([selector]).attrchange({
  trackValues: true, 
  callback: function (e) {
    //console.log( '<p>Attribute <b>' + e.attributeName +'</b> changed from <b>' + e.oldValue +'</b> to <b>' + e.newValue +'</b></p>');
    //event.attributeName - Attribute Name
    //event.oldValue - Prev Value
    //event.newValue - New Value
  }
});
user889030
  • 4,353
  • 3
  • 48
  • 51
1

Just adding and formalizing @David 's solution from above:

Note that jQuery functions are chainable and return 'this' so that multiple invocations can be called one after the other (e.g $container.css("overflow", "hidden").css("outline", 0);).

So the improved code should be:

(function() {
    var ev = new $.Event('style'),
        orig = $.fn.css;
    $.fn.css = function() {
        var ret = orig.apply(this, arguments);
        $(this).trigger(ev);
        return ret; // must include this
    }
})();
Nir Hemed
  • 11
  • 2
0

How about jQuery cssHooks?

Maybe I do not understand the question, but what you are searching for is easily done with cssHooks, without changing css() function.

copy from documentation:

(function( $ ) {

// First, check to see if cssHooks are supported
if ( !$.cssHooks ) {
  // If not, output an error message
  throw( new Error( "jQuery 1.4.3 or above is required for this plugin to work" ) );
}

// Wrap in a document ready call, because jQuery writes
// cssHooks at this time and will blow away your functions
// if they exist.
$(function () {
  $.cssHooks[ "someCSSProp" ] = {
    get: function( elem, computed, extra ) {
      // Handle getting the CSS property
    },
    set: function( elem, value ) {
      // Handle setting the CSS value
    }
  };
});

})( jQuery ); 

https://api.jquery.com/jQuery.cssHooks/

halfbit
  • 3,773
  • 2
  • 34
  • 47
-1

I had the same problem, so I wrote this. It works rather well. Looks great if you mix it with some CSS transitions.

function toggle_visibility(id) {
   var e = document.getElementById("mjwelcome");
   if(e.style.height == '')
      e.style.height = '0px';
   else
      e.style.height = '';
}
KingLouie
  • 399
  • 1
  • 5
  • 13