Using Modernizr for Feature Detection in jQuery

Feature detection is an essential practice in modern web development that ensures web applications degrade gracefully across different browsers without relying on fragile user-agent sniffing. This article explains how to implement feature detection using Modernizr in conjunction with modern jQuery, demonstrating how to conditionally run jQuery methods, target browser capabilities through CSS classes, and dynamically handle fallbacks.

Setting Up Modernizr and Modern jQuery

To use both libraries effectively, Modernizr should generally load in the <head> of your document to prevent a Flash of Unstyled Content (FOUC), as it immediately appends feature-support classes to the <html> element. Modern jQuery should be included either deferred in the <head> or just before the closing </body> tag.

<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
  <meta charset="UTF-8">
  <title>Feature Detection Example</title>
  <script src="modernizr-custom.js"></script>
</head>
<body>
  <!-- Page Content -->
  <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
  <script src="main.js"></script>
</body>
</html>

Note: Replacing class="no-js" with class="js" happens automatically when Modernizr executes.

Method 1: JavaScript Conditional Branching

Modernizr exposes a global Modernizr object that contains boolean properties representing whether the user's browser supports specific HTML5, CSS3, or JavaScript features. You can query these properties directly inside your jQuery code to conditionally execute DOM manipulations or attach event listeners.

$(function() {
  // Check for Local Storage support
  if (Modernizr.localstorage) {
    $('#save-btn').on('click', function() {
      localStorage.setItem('userDraft', $('#editor').val());
    });
  } else {
    // Fallback behavior using server-side session or cookies via jQuery
    $('#save-btn').on('click', function() {
      $.post('/api/save-draft', { draft: $('#editor').val() });
    });
  }

  // Check for Geolocation support
  if (Modernizr.geolocation) {
    navigator.geolocation.getCurrentPosition(function(position) {
      $('#location-display').text(
        `Latitude: ${position.coords.latitude}, Longitude: ${position.coords.longitude}`
      );
    });
  } else {
    $('#location-display').text('Geolocation is not supported by your browser.');
  }
});

Method 2: Selecting Elements Based on Modernizr HTML Classes

Modernizr adds descriptive classes directly to the root <html> element, such as .flexbox or .no-flexbox, and .webp or .no-webp. You can leverage standard jQuery selectors to target elements based on these classes.

$(function() {
  // Check root class via jQuery
  if ($('html').hasClass('no-touchevents')) {
    // Enable hover-based tooltips only on non-touch devices
    $('.tooltip').on('mouseenter mouseleave', function() {
      $(this).find('.tooltip-text').toggle();
    });
  } else {
    // Enable tap/click toggles for touch devices
    $('.tooltip').on('click', function(e) {
      e.stopPropagation();
      $(this).find('.tooltip-text').fadeToggle(200);
    });
  }
});

Method 3: Conditional Polyfilling Before Running jQuery Code

When a critical browser feature is missing, Modernizr allows you to conditionally load a polyfill script before running your primary jQuery logic.

$(function() {
  // Conditionally load polyfill if IntersectionObserver is absent
  if (!Modernizr.intersectionobserver) {
    $.getScript('https://polyfill.io/v3/polyfill.min.js?features=IntersectionObserver')
      .done(initializeLazyLoading)
      .fail(function() {
        console.error('Failed to load polyfill.');
      });
  } else {
    initializeLazyLoading();
  }

  function initializeLazyLoading() {
    const observer = new IntersectionObserver(function(entries) {
      entries.forEach(function(entry) {
        if (entry.isIntersecting) {
          const $img = $(entry.target);
          $img.attr('src', $img.data('src')).removeClass('lazy');
          observer.unobserve(entry.target);
        }
      });
    });

    $('img.lazy').each(function() {
      observer.observe(this);
    });
  }
});

Best Practices

  1. Use Custom Builds: Only build and download Modernizr with tests for the features your application actually relies on. Including unnecessary tests increases bundle size.
  2. Combine with Progressive Enhancement: Ensure the page remains fundamentally functional even if JavaScript or specific modern APIs fail to load entirely.
  3. Prefer Feature Detection over Browser Version Checks: Never use jQuery's deprecated $.browser or parse navigator.userAgent. Modernizr checks whether an API actually works, which remains resilient across platform and version changes.