0
# matchMedia addListener Extension
1
2
The matchMedia addListener extension provides event listener functionality for media query state changes. This enables JavaScript code to respond dynamically to viewport changes, such as device orientation or window resizing.
3
4
## Capabilities
5
6
### Enhanced MediaQueryList Interface
7
8
Extends the basic MediaQueryList with listener support for monitoring state changes.
9
10
```javascript { .api }
11
interface MediaQueryListWithListeners extends MediaQueryList {
12
/**
13
* Add listener for media query state changes
14
* Callback is invoked when the query transitions between matching and non-matching
15
* @param listener - Function called when match state changes
16
*/
17
addListener(listener: (mql: MediaQueryList) => void): void;
18
19
/**
20
* Remove previously added listener
21
* @param listener - Exact listener function to remove
22
*/
23
removeListener(listener: (mql: MediaQueryList) => void): void;
24
}
25
```
26
27
### Adding Event Listeners
28
29
Register callbacks that fire when media query match state transitions occur.
30
31
```javascript { .api }
32
/**
33
* Add listener for media query state changes
34
* The listener is called only when the query transitions between matching and non-matching states
35
* @param listener - Callback function receiving the MediaQueryList object
36
*/
37
addListener(listener: (mql: MediaQueryList) => void): void;
38
```
39
40
**Usage Examples:**
41
42
```javascript
43
// Create a media query with listener support
44
var desktopQuery = matchMedia('(min-width: 1024px)');
45
46
// Add listener for desktop/mobile transitions
47
desktopQuery.addListener(function(mql) {
48
if (mql.matches) {
49
console.log('Switched to desktop view');
50
// Enable desktop-specific features
51
enableDesktopMenu();
52
loadDesktopWidgets();
53
} else {
54
console.log('Switched to mobile view');
55
// Enable mobile-specific features
56
enableMobileMenu();
57
unloadDesktopWidgets();
58
}
59
});
60
61
// Orientation change detection
62
var landscapeQuery = matchMedia('(orientation: landscape)');
63
landscapeQuery.addListener(function(mql) {
64
if (mql.matches) {
65
document.body.classList.add('landscape');
66
document.body.classList.remove('portrait');
67
} else {
68
document.body.classList.add('portrait');
69
document.body.classList.remove('landscape');
70
}
71
});
72
```
73
74
### Removing Event Listeners
75
76
Remove previously registered listeners to prevent memory leaks.
77
78
```javascript { .api }
79
/**
80
* Remove previously added listener
81
* Must pass the exact same function reference that was used with addListener
82
* @param listener - Previously registered listener function
83
*/
84
removeListener(listener: (mql: MediaQueryList) => void): void;
85
```
86
87
**Usage Examples:**
88
89
```javascript
90
// Store reference to listener function
91
function handleDesktopChange(mql) {
92
if (mql.matches) {
93
enableDesktopFeatures();
94
} else {
95
disableDesktopFeatures();
96
}
97
}
98
99
var desktopQuery = matchMedia('(min-width: 1024px)');
100
101
// Add listener
102
desktopQuery.addListener(handleDesktopChange);
103
104
// Later, remove the listener
105
desktopQuery.removeListener(handleDesktopChange);
106
107
// Example: Cleanup when component unmounts
108
function cleanup() {
109
desktopQuery.removeListener(handleDesktopChange);
110
tabletQuery.removeListener(handleTabletChange);
111
mobileQuery.removeListener(handleMobileChange);
112
}
113
```
114
115
## Event System Architecture
116
117
### Debounced Resize Handling
118
119
The listener system includes built-in debouncing to prevent excessive callback execution during window resizing.
120
121
- **Debounce Delay**: 30ms
122
- **Event Source**: Window resize events
123
- **Trigger Condition**: Only when match state actually changes
124
125
### Listener Management
126
127
```javascript
128
// Internal listener tracking (conceptual)
129
var queries = []; // Array of {mql, listeners} objects
130
var isListening = false; // Global resize listener state
131
var timeoutID = 0; // Debounce timeout reference
132
```
133
134
### State Transition Detection
135
136
Listeners are only called when the media query transitions between matching and non-matching states:
137
138
```javascript
139
// Example transition scenarios:
140
141
// Window resized from 800px to 1200px width
142
// Query: '(min-width: 1024px)'
143
// Result: Listener called with matches: true
144
145
// Window resized from 1200px to 900px width
146
// Query: '(min-width: 1024px)'
147
// Result: Listener called with matches: false
148
149
// Window resized from 800px to 850px width
150
// Query: '(min-width: 1024px)'
151
// Result: No listener call (state unchanged)
152
```
153
154
## Common Usage Patterns
155
156
### Responsive Component Initialization
157
158
```javascript
159
function initResponsiveComponent() {
160
var breakpoints = {
161
mobile: matchMedia('(max-width: 767px)'),
162
tablet: matchMedia('(min-width: 768px) and (max-width: 1023px)'),
163
desktop: matchMedia('(min-width: 1024px)')
164
};
165
166
// Set up listeners for each breakpoint
167
breakpoints.mobile.addListener(function(mql) {
168
if (mql.matches) {
169
switchToMobileLayout();
170
}
171
});
172
173
breakpoints.tablet.addListener(function(mql) {
174
if (mql.matches) {
175
switchToTabletLayout();
176
}
177
});
178
179
breakpoints.desktop.addListener(function(mql) {
180
if (mql.matches) {
181
switchToDesktopLayout();
182
}
183
});
184
185
// Initialize with current state
186
if (breakpoints.desktop.matches) {
187
switchToDesktopLayout();
188
} else if (breakpoints.tablet.matches) {
189
switchToTabletLayout();
190
} else {
191
switchToMobileLayout();
192
}
193
}
194
```
195
196
### Dynamic Content Loading
197
198
```javascript
199
var highDPIQuery = matchMedia('(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)');
200
201
highDPIQuery.addListener(function(mql) {
202
var images = document.querySelectorAll('img[data-src-2x]');
203
204
images.forEach(function(img) {
205
if (mql.matches) {
206
// Load high-resolution images
207
img.src = img.getAttribute('data-src-2x');
208
} else {
209
// Load standard resolution images
210
img.src = img.getAttribute('data-src');
211
}
212
});
213
});
214
```
215
216
### Print Style Handling
217
218
```javascript
219
var printQuery = matchMedia('print');
220
221
printQuery.addListener(function(mql) {
222
if (mql.matches) {
223
// Prepare for print
224
hidePrintIncompatibleElements();
225
expandCollapsedSections();
226
generatePrintFooter();
227
} else {
228
// Return from print preview
229
showPrintIncompatibleElements();
230
restoreCollapsedSections();
231
removePrintFooter();
232
}
233
});
234
```
235
236
## Browser Compatibility
237
238
### Native Support
239
240
- **Modern Browsers**: Use native addListener/removeListener when available
241
- **Early Exit**: Polyfill detects native support and defers to it
242
- **IE 9+**: Native matchMedia with addListener support
243
- **Safari 5.1+**: Native support
244
- **Chrome 9+**: Native support
245
- **Firefox 6+**: Native support
246
247
### Polyfill Behavior
248
249
- **IE 6-8**: Full polyfill functionality with resize event monitoring
250
- **Feature Detection**: Automatically detects if addListener is already supported
251
- **Graceful Degradation**: No errors in browsers without CSS3 media query support
252
253
### Conditional Initialization
254
255
```javascript
256
// Check for native addListener support
257
if (window.matchMedia && matchMedia('all').addListener) {
258
console.log('Native addListener support detected');
259
} else {
260
console.log('Using addListener polyfill');
261
}
262
263
// The polyfill handles this automatically
264
var mql = matchMedia('(min-width: 768px)');
265
mql.addListener(callback); // Works in all browsers
266
```
267
268
## Performance Considerations
269
270
### Memory Management
271
272
```javascript
273
// Good: Store listener references for cleanup
274
var listeners = [];
275
276
function addResponsiveListener(query, handler) {
277
var mql = matchMedia(query);
278
mql.addListener(handler);
279
listeners.push({mql: mql, handler: handler});
280
}
281
282
function removeAllListeners() {
283
listeners.forEach(function(item) {
284
item.mql.removeListener(item.handler);
285
});
286
listeners = [];
287
}
288
```
289
290
### Throttling and Debouncing
291
292
The built-in 30ms debouncing is usually sufficient, but for expensive operations, add additional throttling:
293
294
```javascript
295
var desktopQuery = matchMedia('(min-width: 1024px)');
296
var isProcessing = false;
297
298
desktopQuery.addListener(function(mql) {
299
if (isProcessing) return;
300
301
isProcessing = true;
302
303
setTimeout(function() {
304
// Expensive layout operations
305
if (mql.matches) {
306
initializeDesktopComponents();
307
} else {
308
teardownDesktopComponents();
309
}
310
311
isProcessing = false;
312
}, 100);
313
});
314
```