Skip to content

Commit df6a121

Browse files
committed
Adding MutationObservers support
1 parent 9931ad8 commit df6a121

File tree

3 files changed

+278
-178
lines changed

3 files changed

+278
-178
lines changed

README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ javascript-detect-element-resize
33

44
A Cross-Browser, Event-based, Element Resize Detection.
55

6-
In short, this implementation does NOT use an internal timer to detect size changes (as most implementations I found).
6+
In short, this implementation does NOT use an internal timer to detect size changes (as most implementations I found do).
7+
It uses [MutationObservers][4] if supported by the browser, and [overflow and underflow events][2] if not. It also uses the ['onresize' event][5] on IE10 and below.
78

89
About the libraries
910
===================
@@ -14,7 +15,9 @@ I was searching for a library that allowed me to detect when an DOM element chan
1415

1516
Then I came across this [great post][1] on [Back Alley Coder][3] about using [overflow and underflow events][2] to do event-based element resize detection; and it works great without consuming resources at all (just like any other browser originated event).
1617

17-
The libraries on this repository are just a concrete implementation of that technique.
18+
On the other hand, most new browsers implement [WC3 DOM4 MutationObservers][4], which allows to observe property changes and hence indirectly watch for resize changes.
19+
20+
The libraries on this repository are just a concrete implementation of a mixture of the above technologies/techniques.
1821

1922
Libraries
2023
=========
@@ -40,9 +43,12 @@ jQuery plugin library usage
4043
<script type="text/javascript" src="jquery.js"></script>
4144
<script type="text/javascript" src="jquery.resize.js"></script>
4245
<script type="text/javascript">
43-
$('#resizeElement').resize(function() {
46+
var myFunc = function() {
4447
/* do something */
45-
});
48+
};
49+
50+
$('#resizeElement').resize(myFunc);
51+
$('#resizeElement').removeResize(myFunc);
4652
</script>
4753
```
4854

@@ -67,6 +73,13 @@ TODO
6773

6874
Release Notes
6975
=============
76+
v0.3
77+
----
78+
79+
- Adds support for MutationObservers.
80+
- Adds support for IE 11.
81+
- Wrapped the pure javascript version of the library (to hide non-public methods).
82+
7083
v0.2
7184
----
7285

@@ -99,3 +112,5 @@ External links
99112
[1]: http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/
100113
[2]: http://www.backalleycoder.com/2013/03/14/oft-overlooked-overflow-and-underflow-events/
101114
[3]: http://www.backalleycoder.com/
115+
[4]: http://www.w3.org/TR/dom/#mutation-observers
116+
[5]: http://msdn.microsoft.com/en-us/library/ie/ms536959

detect-element-resize.js

Lines changed: 155 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -4,133 +4,175 @@
44
* https://github.com/sdecima/javascript-detect-element-resize
55
* Sebastian Decima
66
*
7-
* version: 0.2
7+
* version: 0.3
88
**/
99

10-
function addFlowListener(element, type, fn){
11-
var flow = type == 'over';
12-
element.addEventListener('OverflowEvent' in window ? 'overflowchanged' : type + 'flow', function(e){
13-
if (e.type == (type + 'flow') ||
14-
((e.orient == 0 && e.horizontalOverflow == flow) ||
15-
(e.orient == 1 && e.verticalOverflow == flow) ||
16-
(e.orient == 2 && e.horizontalOverflow == flow && e.verticalOverflow == flow))) {
17-
e.flow = type;
18-
return fn.call(this, e);
10+
(function ( $ ) {
11+
function addFlowListener(element, type, fn){
12+
var flow = type == 'over';
13+
element.addEventListener('OverflowEvent' in window ? 'overflowchanged' : type + 'flow', function(e){
14+
if (e.type == (type + 'flow') ||
15+
((e.orient == 0 && e.horizontalOverflow == flow) ||
16+
(e.orient == 1 && e.verticalOverflow == flow) ||
17+
(e.orient == 2 && e.horizontalOverflow == flow && e.verticalOverflow == flow))) {
18+
e.flow = type;
19+
return fn.call(this, e);
20+
}
21+
}, false);
22+
};
23+
24+
function newResizeMutationObserverCallback(element, fn) {
25+
var oldWidth = element.clientWidth,
26+
oldHeight = element.clientHeight;
27+
return function() {
28+
if(oldWidth != element.clientWidth || oldHeight != element.clientHeight) {
29+
oldWidth = element.clientWidth;
30+
oldHeight = element.clientHeight;
31+
fn.call(element);
32+
}
1933
}
20-
}, false);
21-
};
34+
}
35+
36+
function addResizeMutationObserver(element, fn){
37+
var observer = new MutationObserver(newResizeMutationObserverCallback(element, fn));
38+
observer.observe(element, {
39+
attributes: true,
40+
subtree: true,
41+
attributeFilter: ['style']
42+
});
43+
return observer;
44+
};
2245

23-
function fireEvent(element, type, data, options){
24-
var options = options || {},
25-
event = document.createEvent('Event');
26-
event.initEvent(type, 'bubbles' in options ? options.bubbles : true, 'cancelable' in options ? options.cancelable : true);
27-
for (var z in data) event[z] = data[z];
28-
element.dispatchEvent(event);
29-
};
46+
function fireEvent(element, type, data, options){
47+
var options = options || {},
48+
event = document.createEvent('Event');
49+
event.initEvent(type, 'bubbles' in options ? options.bubbles : true, 'cancelable' in options ? options.cancelable : true);
50+
for (var z in data) event[z] = data[z];
51+
element.dispatchEvent(event);
52+
};
3053

31-
function addResizeListener(element, fn){
32-
var resize = 'onresize' in element;
33-
if (!resize && !element._resizeSensor) {
34-
var sensor_style = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; z-index: -1;';
35-
var sensor = element._resizeSensor = document.createElement('div');
36-
sensor.className = 'resize-sensor';
37-
//sensor.style = ;
38-
sensor.innerHTML = '<div class="resize-overflow" style="' + sensor_style
39-
+ '"><div></div></div><div class="resize-underflow" style="' + sensor_style
40-
+ '"><div></div></div>';
41-
42-
var x = 0, y = 0,
43-
first = sensor.firstElementChild.firstChild,
44-
last = sensor.lastElementChild.firstChild,
45-
matchFlow = function(event){
46-
var change = false,
47-
width = element.offsetWidth;
48-
if (x != width) {
49-
first.style.width = width - 1 + 'px';
50-
last.style.width = width + 1 + 'px';
51-
change = true;
52-
x = width;
53-
}
54-
var height = element.offsetHeight;
55-
if (y != height) {
56-
first.style.height = height - 1 + 'px';
57-
last.style.height = height + 1 + 'px';
58-
change = true;
59-
y = height;
54+
function addResizeListener(element, fn){
55+
if ('MutationObserver' in window) {
56+
fn._mutationObserver = addResizeMutationObserver(element, fn);
57+
var events = element._mutationObservers || (element._mutationObservers = []);
58+
if (indexOf.call(events, fn) == -1) events.push(fn);
59+
} else {
60+
var resize = 'onresize' in element;
61+
if (!resize && !element._resizeSensor) {
62+
var sensor_style = 'position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; z-index: -1;';
63+
var sensor = element._resizeSensor = document.createElement('div');
64+
sensor.className = 'resize-sensor';
65+
sensor.innerHTML = '<div class="resize-overflow" style="' + sensor_style
66+
+ '"><div></div></div><div class="resize-underflow" style="' + sensor_style
67+
+ '"><div></div></div>';
68+
69+
var x = 0, y = 0,
70+
first = sensor.firstElementChild.firstChild,
71+
last = sensor.lastElementChild.firstChild,
72+
matchFlow = function(event){
73+
var change = false,
74+
width = element.offsetWidth;
75+
if (x != width) {
76+
first.style.width = width - 1 + 'px';
77+
last.style.width = width + 1 + 'px';
78+
change = true;
79+
x = width;
80+
}
81+
var height = element.offsetHeight;
82+
if (y != height) {
83+
first.style.height = height - 1 + 'px';
84+
last.style.height = height + 1 + 'px';
85+
change = true;
86+
y = height;
87+
}
88+
if (change && event.currentTarget != element) fireEvent(element, 'resize');
89+
};
90+
91+
if (getComputedStyle(element).position == 'static'){
92+
element.style.position = 'relative';
93+
element._resizeSensor._resetPosition = true;
6094
}
61-
if (change && event.currentTarget != element) fireEvent(element, 'resize');
95+
addFlowListener(sensor, 'over', matchFlow);
96+
addFlowListener(sensor, 'under', matchFlow);
97+
addFlowListener(sensor.firstElementChild, 'over', matchFlow);
98+
addFlowListener(sensor.lastElementChild, 'under', matchFlow);
99+
element.appendChild(sensor);
100+
matchFlow({});
101+
}
102+
var events = element._flowEvents || (element._flowEvents = []);
103+
if (indexOf.call(events, fn) == -1) events.push(fn);
104+
if (!resize) element.addEventListener('resize', fn, false);
105+
element.onresize = function(e){
106+
forEach.call(events, function(fn){
107+
fn.call(element, e);
108+
});
62109
};
63-
64-
if (getComputedStyle(element).position == 'static'){
65-
element.style.position = 'relative';
66-
element._resizeSensor._resetPosition = true;
67110
}
68-
addFlowListener(sensor, 'over', matchFlow);
69-
addFlowListener(sensor, 'under', matchFlow);
70-
addFlowListener(sensor.firstElementChild, 'over', matchFlow);
71-
addFlowListener(sensor.lastElementChild, 'under', matchFlow);
72-
element.appendChild(sensor);
73-
matchFlow({});
74-
}
75-
var events = element._flowEvents || (element._flowEvents = []);
76-
if (indexOf.call(events, fn) == -1) events.push(fn);
77-
if (!resize) element.addEventListener('resize', fn, false);
78-
element.onresize = function(e){
79-
forEach.call(events, function(fn){
80-
fn.call(element, e);
81-
});
82111
};
83-
};
84112

85-
function removeResizeListener(element, fn){
86-
var index = indexOf.call(element._flowEvents, fn);
87-
if (index > -1) element._flowEvents.splice(index, 1);
88-
if (!element._flowEvents.length) {
89-
var sensor = element._resizeSensor;
90-
if (sensor) {
91-
element.removeChild(sensor);
92-
if (sensor._resetPosition) element.style.position = 'static';
93-
delete element._resizeSensor;
113+
function removeResizeListener(element, fn){
114+
if ('MutationObserver' in window) {
115+
var index = indexOf.call(element._mutationObservers, fn);
116+
if (index > -1) {
117+
var observer = element._mutationObservers[index]._mutationObserver;
118+
element._mutationObservers.splice(index, 1);
119+
observer.disconnect();
120+
}
121+
} else {
122+
var resize = 'onresize' in element;
123+
var index = indexOf.call(element._flowEvents, fn);
124+
if (index > -1) element._flowEvents.splice(index, 1);
125+
if (!element._flowEvents.length) {
126+
var sensor = element._resizeSensor;
127+
if (sensor) {
128+
element.removeChild(sensor);
129+
if (sensor._resetPosition) element.style.position = 'static';
130+
try { delete element._resizeSensor; } catch(e) { /* delete arrays not supported on IE 7 and below */}
131+
}
132+
if (resize) element.onresize = null;
133+
try { delete element._flowEvents; } catch(e) { /* delete arrays not supported on IE 7 and below */}
134+
}
135+
if(!resize) element.removeEventListener('resize', fn);
94136
}
95-
if ('onresize' in element) element.onresize = null;
96-
delete element._flowEvents;
97-
}
98-
element.removeEventListener('resize', fn);
99-
};
137+
};
100138

101-
/* Array.indexOf for IE < 9 */
102-
var indexOf = function(needle) {
103-
if(typeof Array.prototype.indexOf === 'function') {
104-
indexOf = Array.prototype.indexOf;
105-
} else {
106-
indexOf = function(needle) {
107-
var i = -1, index = -1;
139+
/* Array.indexOf for IE < 9 */
140+
var indexOf = function(needle) {
141+
if(typeof Array.prototype.indexOf === 'function') {
142+
indexOf = Array.prototype.indexOf;
143+
} else {
144+
indexOf = function(needle) {
145+
var i = -1, index = -1;
108146

109-
for(i = 0; i < this.length; i++) {
110-
if(this[i] === needle) {
111-
index = i;
112-
break;
113-
}
114-
}
147+
for(i = 0; i < this.length; i++) {
148+
if(this[i] === needle) {
149+
index = i;
150+
break;
151+
}
152+
}
115153

116-
return index;
117-
};
118-
}
154+
return index;
155+
};
156+
}
119157

120-
return indexOf.call(this, needle);
121-
};
158+
return indexOf.call(this, needle);
159+
};
122160

123-
/* Array.forEach for IE < 9 */
124-
var forEach = function(action, that) {
125-
if(typeof Array.prototype.forEach === 'function') {
126-
forEach = Array.prototype.forEach;
127-
} else {
128-
forEach = function(action, that) {
129-
for (var i= 0, n= this.length; i<n; i++)
130-
if (i in this)
131-
action.call(that, this[i], i, this);
132-
};
133-
}
161+
/* Array.forEach for IE < 9 */
162+
var forEach = function(action, that) {
163+
if(typeof Array.prototype.forEach === 'function') {
164+
forEach = Array.prototype.forEach;
165+
} else {
166+
forEach = function(action, that) {
167+
for (var i= 0, n= this.length; i<n; i++)
168+
if (i in this)
169+
action.call(that, this[i], i, this);
170+
};
171+
}
134172

135-
return forEach.call(this, action, that);
136-
};
173+
return forEach.call(this, action, that);
174+
};
175+
176+
window.addResizeListener = addResizeListener;
177+
window.removeResizeListener = removeResizeListener;
178+
}());

0 commit comments

Comments
 (0)