or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-polyfill.mdcross-domain-proxy.mdindex.mdmatchmedia-listeners.mdmatchmedia-polyfill.md

matchmedia-listeners.mddocs/

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

```