147

I've got a div that contains some content that's being added and removed dynamically, so its height is changing often. I also have a div that is absolutely positioned directly underneath with javascript, so unless I can detect when the height of the div changes, I can't reposition the div below it.

So, how can I detect when the height of that div changes? I assume there's some jQuery event I need to use, but I'm not sure which one to hook into.

T J
  • 42,762
  • 13
  • 83
  • 138
Bob Somers
  • 7,266
  • 5
  • 42
  • 46
  • 4
    I wonder why one would not want to use a plugin when using jQuery. – Andre Calil Aug 02 '13 at 13:16
  • 1
    @user007 What makes your elements size changed? – A. Wolff Aug 02 '13 at 17:28
  • @roasted my div height changes when some item appended to it, append is done by jquery – user007 Aug 03 '13 at 11:00
  • 1
    @user007 so when you append something to the DIV, you could trigger a custom resize event for the DIV. An other way (worst IMO) could be to use a custom method for appending content which simply extend native jquery append() method, use it with a callback e.g – A. Wolff Aug 03 '13 at 11:15

10 Answers10

68

Use a resize sensor from the css-element-queries library:

https://github.com/marcj/css-element-queries

new ResizeSensor(jQuery('#myElement'), function() {
    console.log('myelement has been resized');
});

It uses a event based approach and doesn't waste your cpu time. Works in all browsers incl. IE7+.

Marc J. Schmidt
  • 8,302
  • 4
  • 34
  • 33
  • 5
    nice solution, it uses a invisible new element as an overlay on the desired target and utilize the "scroll" event on the overlay to trigger changes. – Eike Thies Nov 20 '14 at 17:59
  • 2
    This is the only solution that works in all cases, including when content and attributes does not change but dimensions changes (example: percentage dimensions using css) – FF_Dev Feb 18 '15 at 11:42
  • 2
    Best solution by far. – dlsso Apr 22 '16 at 18:45
  • 1
    This not being the top voted answer on here is a tragedy. This really works 100%. – Jimbo Jonny May 27 '16 at 08:47
  • This doesn't seem to work for elements which start off hidden. – greatwitenorth Feb 23 '17 at 20:11
  • I tried this and it works great, but I need to attach this event on multiple elements and then get the handle of the target element inside my callback function, please give me some pointers regarding how to achieve this. Thanks ! – Tarun Gupta Jul 11 '17 at 12:52
  • `This doesn't seem to work for elements which start off hidden` Works meanwhile in v1.0.5. – Marc J. Schmidt Oct 22 '18 at 09:09
48

I wrote a plugin sometime back for attrchange listener which basically adds a listener function on attribute change. Even though I say it as a plugin, actually it is a simple function written as a jQuery plugin.. so if you want.. strip off the plugin specfic code and use the core functions.

Note: This code doesn't use polling

check out this simple demo http://jsfiddle.net/aD49d/

$(function () {
    var prevHeight = $('#test').height();
    $('#test').attrchange({
        callback: function (e) {
            var curHeight = $(this).height();            
            if (prevHeight !== curHeight) {
               $('#logger').text('height changed from ' + prevHeight + ' to ' + curHeight);

                prevHeight = curHeight;
            }            
        }
    }).resizable();
});

Plugin page: http://meetselva.github.io/attrchange/

Minified version: (1.68kb)

(function(e){function t(){var e=document.createElement("p");var t=false;if(e.addEventListener)e.addEventListener("DOMAttrModified",function(){t=true},false);else if(e.attachEvent)e.attachEvent("onDOMAttrModified",function(){t=true});else return false;e.setAttribute("id","target");return t}function n(t,n){if(t){var r=this.data("attr-old-value");if(n.attributeName.indexOf("style")>=0){if(!r["style"])r["style"]={};var i=n.attributeName.split(".");n.attributeName=i[0];n.oldValue=r["style"][i[1]];n.newValue=i[1]+":"+this.prop("style")[e.camelCase(i[1])];r["style"][i[1]]=n.newValue}else{n.oldValue=r[n.attributeName];n.newValue=this.attr(n.attributeName);r[n.attributeName]=n.newValue}this.data("attr-old-value",r)}}var r=window.MutationObserver||window.WebKitMutationObserver;e.fn.attrchange=function(i){var s={trackValues:false,callback:e.noop};if(typeof i==="function"){s.callback=i}else{e.extend(s,i)}if(s.trackValues){e(this).each(function(t,n){var r={};for(var i,t=0,s=n.attributes,o=s.length;t<o;t++){i=s.item(t);r[i.nodeName]=i.value}e(this).data("attr-old-value",r)})}if(r){var o={subtree:false,attributes:true,attributeOldValue:s.trackValues};var u=new r(function(t){t.forEach(function(t){var n=t.target;if(s.trackValues){t.newValue=e(n).attr(t.attributeName)}s.callback.call(n,t)})});return this.each(function(){u.observe(this,o)})}else if(t()){return this.on("DOMAttrModified",function(e){if(e.originalEvent)e=e.originalEvent;e.attributeName=e.attrName;e.oldValue=e.prevValue;s.callback.call(this,e)})}else if("onpropertychange"in document.body){return this.on("propertychange",function(t){t.attributeName=window.event.propertyName;n.call(e(this),s.trackValues,t);s.callback.call(this,t)})}return this}})(jQuery)
Selvakumar Arumugam
  • 79,297
  • 15
  • 120
  • 134
  • 26
    This solves the problem if you resize the div manually, this doesn't solve the original question no? If you add content dynamically it doesn't detect the height change: http://jsfiddle.net/aD49d/25/ – smets.kevin Sep 11 '13 at 06:37
  • I would also be interested in knowing if there was a way to make this work for dynamic content. – EricP Sep 22 '13 at 18:28
  • 4
    @JoeCoder This attrchange code rely on DOM events triggered by the browser when there is an user action. However, dynamic contents in that example are included programmatically which unfortunately doesn't trigger any events. "Polling" seems to be another easier option here.. other option would be able to trigger manually after the dynamic content is included. – Selvakumar Arumugam Sep 22 '13 at 20:12
29

You can use the DOMSubtreeModified event

$(something).bind('DOMSubtreeModified' ...

But this will fire even if the dimensions don't change, and reassigning the position whenever it fires can take a performance hit. In my experience using this method, checking whether the dimensions have changed is less expensive and so you might consider combining the two.

Or if you are directly altering the div (rather than the div being altered by user input in unpredictable ways, like if it is contentEditable), you can simply fire a custom event whenever you do so.

Downside: IE and Opera don't implement this event.

eyelidlessness
  • 62,413
  • 11
  • 90
  • 94
23

This is how I recently handled this problem:

$('#your-resizing-div').bind('getheight', function() {
    $('#your-resizing-div').height();
});

function your_function_to_load_content() {
    /*whatever your thing does*/
    $('#your-resizing-div').trigger('getheight');
}

I know I'm a few years late to the party, just think my answer may help some people in the future, without having to download any plugins.

Kyle
  • 410
  • 3
  • 9
  • 1
    I guess in some ways this isn't the best way to do it, but I actually quite like it, as it means you can trigger it as a callback at the end of an animation. The resize plugin will keep firing throughout an animation, which could be helpful, but could cause problems too. This method at least gives you control of when to fire the height check. – andyface Jul 12 '13 at 09:51
  • What exactly your code does? Why we cant just use the code you write inside bind event in the function insted of binding and triggering? – user1447420 Jun 19 '15 at 12:25
  • But this is not automatic, isn't it? – Albert Català Sep 09 '15 at 10:19
  • This answer would improve immensely with clarifying text! Explaining what the bind does, when the function is triggered etc. Rather a cryptic code response. – Bernd Wechner May 11 '21 at 02:40
22

You can use MutationObserver class.

MutationObserver provides developers a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.

Example (source)

// select the target node
var target = document.querySelector('#some-id');

// create an observer instance
var observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    console.log(mutation.type);
  });    
});

// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true };

// pass in the target node, as well as the observer options
observer.observe(target, config);

// later, you can stop observing
observer.disconnect();
EFernandes
  • 1,336
  • 12
  • 11
  • This is the correct answer. Also take a look at MutationSummary, a library that builds on MutationObservers: https://github.com/rafaelw/mutation-summary – Christian Bankester Sep 30 '15 at 13:10
7

In response to user007:

If the height of your element is changing due to items being appended to it using .append() you shouldn't need to detect the change in height. Simply add the reposition of your second element in the same function where you are appending the new content to your first element.

As in:

Working Example

$('.class1').click(function () {
    $('.class1').append("<div class='newClass'><h1>This is some content</h1></div>");
    $('.class2').css('top', $('.class1').offset().top + $('.class1').outerHeight());
});
apaul
  • 16,092
  • 8
  • 47
  • 82
3

These days you can also use the Web API ResizeObserver.

Simple example:

const myElement = document.querySelector('#myElement');

const resizeObserver = new ResizeObserver(() => {
    console.log('size of myElement changed');
});

resizeObserver.observe(myElement);
gitaarik
  • 42,736
  • 12
  • 98
  • 105
2

You can make a simple setInterval.

function someJsClass()
{
  var _resizeInterval = null;
  var _lastHeight = 0;
  var _lastWidth = 0;
  
  this.Initialize = function(){
    var _resizeInterval = setInterval(_resizeIntervalTick, 200);
  };
  
  this.Stop = function(){
    if(_resizeInterval != null)
      clearInterval(_resizeInterval);
  };
  
  var _resizeIntervalTick = function () {
    if ($(yourDiv).width() != _lastWidth || $(yourDiv).height() != _lastHeight) {
      _lastWidth = $(contentBox).width();
      _lastHeight = $(contentBox).height();
      DoWhatYouWantWhenTheSizeChange();
    }
  };
}

var class = new someJsClass();
class.Initialize();

EDIT:

This is a example with a class. But you can do something easiest.

Estefano Salazar
  • 419
  • 4
  • 15
0

You can use this, but it only supports Firefox and Chrome.

$(element).bind('DOMSubtreeModified', function () {
  var $this = this;
  var updateHeight = function () {
    var Height = $($this).height();
    console.log(Height);
  };
  setTimeout(updateHeight, 2000);
});
Pang
  • 9,564
  • 146
  • 81
  • 122
0

Pretty basic but works:

function dynamicHeight() {
    var height = jQuery('').height();
    jQuery('.edito-wrapper').css('height', editoHeight);
}
editoHeightSize();

jQuery(window).resize(function () {
    editoHeightSize();
});
Zac
  • 121
  • 2
  • 10
  • this will only be fired if the window is resized, but OP want to bind a resize event to a specific container – Fanie Void Feb 28 '19 at 13:35