DOM Elements! Y U No Resize Event?
During your coding adventures, you may have run into occasions where you wanted to know when an element in your document changed dimensions – basically the window resize event, but on regular elements. Element size changes can occur for many reasons: modifications to CSS width, height, padding, as a response to changes to a parent element’s size, and many more. Before today, you probably thought this was mere unicorn lore, an impossible feat – well buckle up folks, we’re about to throw down the gauntlet.
Eye of Newt, and Toe of Frog
Before we start adding bits to our hacker’s cauldron, let’s review which browsers this will target…you might have just thought to yourself “well, all of them, right?” – but you’d be wrong. This hack is only needed for WebKit and Firefox. In a twist of fate, IE offers built-in support for resize events on regular DOM elements – I shit you not, see for yourself: http://msdn.microsoft.com/en-us/library/ie/ms536959%28v=vs.85%29.aspx.
Now, for the DOM alchemy! We’ll need to add to our cauldron overflow or underflow events. If you haven’t heard of them, that’s OK, because I have just the post to get you up to speed – go ahead, I’ll wait here –> Back Alley Coder: Overflow and Underflow Events.
Whew, you’re back! Now that you’re in the overflow know, you might think this hack is simply setting overflow and underflow on an element, but that doesn’t provide us with the functionality we’re after. Overflow and underflow events only fire when an element changes flow state, not each time an element changes size. We’re going to need a few DOM elements and some very well-placed overflow and underflow events to create what I refer to as ‘sensors’. Let’s take a look at the code you’ll need to make cross-browser element resize events possible:
Resize Sensor HTML
The following HTML block is auto-appended to any element you attach a resize event to. You can only attach resize events to elements that allow children – basically, no elements declared with self-closing tags.
<div class="resize-sensor"> <div class="resize-overflow"><div></div></div> <div class="resize-underflow"><div></div></div> </div>
Resize Sensor CSS
.resize-sensor, .resize-sensor > div { position: absolute; top: 0; left: 0; 100%; height: 100%; overflow: hidden; z-index: -1; }
Resize Event Methods
The following is the JavaScript you’ll need to enable resize event listening. The first two functions are prerequisites that are used in the main addResizeListener
and removeResizeListener
methods. (further details on the addFlowListener
method are available in the overflow/underflow event post, as previous mentioned)
function addFlowListener(element, type, fn){ var flow = type == 'over'; element.addEventListener('OverflowEvent' in window ? 'overflowchanged' : type + 'flow', function(e){ if (e.type == (type + 'flow') || ((e.orient == 0 && e.horizontalOverflow == flow) || (e.orient == 1 && e.verticalOverflow == flow) || (e.orient == 2 && e.horizontalOverflow == flow && e.verticalOverflow == flow))) { e.flow = type; return fn.call(this, e); } }, false); }; function fireEvent(element, type, data, options){ var options = options || {}, event = document.createEvent('Event'); event.initEvent(type, 'bubbles' in options ? options.bubbles : true, 'cancelable' in options ? options.cancelable : true); for (var z in data) event[z] = data[z]; element.dispatchEvent(event); }; function addResizeListener(element, fn){ var resize = 'onresize' in element; if (!resize && !element._resizeSensor) { var sensor = element._resizeSensor = document.createElement('div'); sensor.className = 'resize-sensor'; sensor.innerHTML = '<div class="resize-overflow"><div></div></div><div class="resize-underflow"><div></div></div>'; var x = 0, y = 0, first = sensor.firstElementChild.firstChild, last = sensor.lastElementChild.firstChild, matchFlow = function(event){ var change = false, width = element.offsetWidth; if (x != width) { first.style.width = width - 1 + 'px'; last.style.width = width + 1 + 'px'; change = true; x = width; } var height = element.offsetHeight; if (y != height) { first.style.height = height - 1 + 'px'; last.style.height = height + 1 + 'px'; change = true; y = height; } if (change && event.currentTarget != element) fireEvent(element, 'resize'); }; if (getComputedStyle(element).position == 'static'){ element.style.position = 'relative'; element._resizeSensor._resetPosition = true; } addFlowListener(sensor, 'over', matchFlow); addFlowListener(sensor, 'under', matchFlow); addFlowListener(sensor.firstElementChild, 'over', matchFlow); addFlowListener(sensor.lastElementChild, 'under', matchFlow); element.appendChild(sensor); matchFlow({}); } var events = element._flowEvents || (element._flowEvents = []); if (events.indexOf(fn) == -1) events.push(fn); if (!resize) element.addEventListener('resize', fn, false); element.onresize = function(e){ events.forEach(function(fn){ fn.call(element, e); }); }; }; function removeResizeListener(element, fn){ var index = element._flowEvents.indexOf(fn); if (index > -1) element._flowEvents.splice(index, 1); if (!element._flowEvents.length) { var sensor = element._resizeSensor; if (sensor) { element.removeChild(sensor); if (sensor._resetPosition) element.style.position = 'static'; delete element._resizeSensor; } if ('onresize' in element) element.onresize = null; delete element._flowEvents; } element.removeEventListener('resize', fn); };
Demo-licious!
Here’s a pseudo code usage of the method.
var myElement = document.getElementById('my_element'), myResizeFn = function(){ /* do something on resize */ }; addResizeListener(myElement, myResizeFn); removeResizeListener(myElement, myResizeFn);
Cut to the chase, let’s see this resize thang in action:
Resize ALL The Things!
Now that we’re equipped with a nifty, cross-browser element resize event, what would it be good for? Here’s a few possible uses:
- Resize-proof Web Component UI development
- Per-element responsive design
- Size-based loading of content
- Anything you can imagine!