CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-respond-js

Fast and lightweight polyfill for min/max-width CSS3 Media Queries for IE 6-8 and more

Pending
Overview
Eval results
Files

matchmedia-listeners.mddocs/

matchMedia addListener Extension

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.

Capabilities

Enhanced MediaQueryList Interface

Extends the basic MediaQueryList with listener support for monitoring state changes.

interface MediaQueryListWithListeners extends MediaQueryList {
  /** 
   * Add listener for media query state changes
   * Callback is invoked when the query transitions between matching and non-matching
   * @param listener - Function called when match state changes
   */
  addListener(listener: (mql: MediaQueryList) => void): void;
  
  /** 
   * Remove previously added listener
   * @param listener - Exact listener function to remove
   */
  removeListener(listener: (mql: MediaQueryList) => void): void;
}

Adding Event Listeners

Register callbacks that fire when media query match state transitions occur.

/**
 * Add listener for media query state changes
 * The listener is called only when the query transitions between matching and non-matching states
 * @param listener - Callback function receiving the MediaQueryList object
 */
addListener(listener: (mql: MediaQueryList) => void): void;

Usage Examples:

// Create a media query with listener support
var desktopQuery = matchMedia('(min-width: 1024px)');

// Add listener for desktop/mobile transitions
desktopQuery.addListener(function(mql) {
  if (mql.matches) {
    console.log('Switched to desktop view');
    // Enable desktop-specific features
    enableDesktopMenu();
    loadDesktopWidgets();
  } else {
    console.log('Switched to mobile view');
    // Enable mobile-specific features
    enableMobileMenu();
    unloadDesktopWidgets();
  }
});

// Orientation change detection
var landscapeQuery = matchMedia('(orientation: landscape)');
landscapeQuery.addListener(function(mql) {
  if (mql.matches) {
    document.body.classList.add('landscape');
    document.body.classList.remove('portrait');
  } else {
    document.body.classList.add('portrait');
    document.body.classList.remove('landscape');
  }
});

Removing Event Listeners

Remove previously registered listeners to prevent memory leaks.

/**
 * Remove previously added listener
 * Must pass the exact same function reference that was used with addListener
 * @param listener - Previously registered listener function
 */
removeListener(listener: (mql: MediaQueryList) => void): void;

Usage Examples:

// Store reference to listener function
function handleDesktopChange(mql) {
  if (mql.matches) {
    enableDesktopFeatures();
  } else {
    disableDesktopFeatures();
  }
}

var desktopQuery = matchMedia('(min-width: 1024px)');

// Add listener
desktopQuery.addListener(handleDesktopChange);

// Later, remove the listener
desktopQuery.removeListener(handleDesktopChange);

// Example: Cleanup when component unmounts
function cleanup() {
  desktopQuery.removeListener(handleDesktopChange);
  tabletQuery.removeListener(handleTabletChange);
  mobileQuery.removeListener(handleMobileChange);
}

Event System Architecture

Debounced Resize Handling

The listener system includes built-in debouncing to prevent excessive callback execution during window resizing.

  • Debounce Delay: 30ms
  • Event Source: Window resize events
  • Trigger Condition: Only when match state actually changes

Listener Management

// Internal listener tracking (conceptual)
var queries = []; // Array of {mql, listeners} objects
var isListening = false; // Global resize listener state
var timeoutID = 0; // Debounce timeout reference

State Transition Detection

Listeners are only called when the media query transitions between matching and non-matching states:

// Example transition scenarios:

// Window resized from 800px to 1200px width
// Query: '(min-width: 1024px)'
// Result: Listener called with matches: true

// Window resized from 1200px to 900px width  
// Query: '(min-width: 1024px)'
// Result: Listener called with matches: false

// Window resized from 800px to 850px width
// Query: '(min-width: 1024px)'
// Result: No listener call (state unchanged)

Common Usage Patterns

Responsive Component Initialization

function initResponsiveComponent() {
  var breakpoints = {
    mobile: matchMedia('(max-width: 767px)'),
    tablet: matchMedia('(min-width: 768px) and (max-width: 1023px)'),
    desktop: matchMedia('(min-width: 1024px)')
  };
  
  // Set up listeners for each breakpoint
  breakpoints.mobile.addListener(function(mql) {
    if (mql.matches) {
      switchToMobileLayout();
    }
  });
  
  breakpoints.tablet.addListener(function(mql) {
    if (mql.matches) {
      switchToTabletLayout();
    }
  });
  
  breakpoints.desktop.addListener(function(mql) {
    if (mql.matches) {
      switchToDesktopLayout();
    }
  });
  
  // Initialize with current state
  if (breakpoints.desktop.matches) {
    switchToDesktopLayout();
  } else if (breakpoints.tablet.matches) {
    switchToTabletLayout();
  } else {
    switchToMobileLayout();
  }
}

Dynamic Content Loading

var highDPIQuery = matchMedia('(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)');

highDPIQuery.addListener(function(mql) {
  var images = document.querySelectorAll('img[data-src-2x]');
  
  images.forEach(function(img) {
    if (mql.matches) {
      // Load high-resolution images
      img.src = img.getAttribute('data-src-2x');
    } else {
      // Load standard resolution images
      img.src = img.getAttribute('data-src');
    }
  });
});

Print Style Handling

var printQuery = matchMedia('print');

printQuery.addListener(function(mql) {
  if (mql.matches) {
    // Prepare for print
    hidePrintIncompatibleElements();
    expandCollapsedSections();
    generatePrintFooter();
  } else {
    // Return from print preview
    showPrintIncompatibleElements();
    restoreCollapsedSections();
    removePrintFooter();
  }
});

Browser Compatibility

Native Support

  • Modern Browsers: Use native addListener/removeListener when available
  • Early Exit: Polyfill detects native support and defers to it
  • IE 9+: Native matchMedia with addListener support
  • Safari 5.1+: Native support
  • Chrome 9+: Native support
  • Firefox 6+: Native support

Polyfill Behavior

  • IE 6-8: Full polyfill functionality with resize event monitoring
  • Feature Detection: Automatically detects if addListener is already supported
  • Graceful Degradation: No errors in browsers without CSS3 media query support

Conditional Initialization

// Check for native addListener support
if (window.matchMedia && matchMedia('all').addListener) {
  console.log('Native addListener support detected');
} else {
  console.log('Using addListener polyfill');
}

// The polyfill handles this automatically
var mql = matchMedia('(min-width: 768px)');
mql.addListener(callback); // Works in all browsers

Performance Considerations

Memory Management

// Good: Store listener references for cleanup
var listeners = [];

function addResponsiveListener(query, handler) {
  var mql = matchMedia(query);
  mql.addListener(handler);
  listeners.push({mql: mql, handler: handler});
}

function removeAllListeners() {
  listeners.forEach(function(item) {
    item.mql.removeListener(item.handler);
  });
  listeners = [];
}

Throttling and Debouncing

The built-in 30ms debouncing is usually sufficient, but for expensive operations, add additional throttling:

var desktopQuery = matchMedia('(min-width: 1024px)');
var isProcessing = false;

desktopQuery.addListener(function(mql) {
  if (isProcessing) return;
  
  isProcessing = true;
  
  setTimeout(function() {
    // Expensive layout operations
    if (mql.matches) {
      initializeDesktopComponents();
    } else {
      teardownDesktopComponents();
    }
    
    isProcessing = false;
  }, 100);
});

Install with Tessl CLI

npx tessl i tessl/npm-respond-js

docs

core-polyfill.md

cross-domain-proxy.md

index.md

matchmedia-listeners.md

matchmedia-polyfill.md

tile.json