This JavaScript utility helps your website identify and adapt to different browsers and devices. Here’s what it does in simple terms:

Table of Contents

πŸ” What This Code Does

When someone visits your website, this script:

  1. Identifies their browser (Chrome, Firefox, Safari, Edge, Opera)

  2. Detects their device type (desktop, tablet, or mobile)

  3. Recognizes their operating system (Windows, macOS, iOS, Android, Linux)

  4. Checks screen size and adjusts when the window resizes

  5. Detects touch capabilities and other browser features

πŸ› οΈ How It Works

The script adds helpful CSS classes to your webpage’s <body> element, such as:

  • chrome-browser or firefox-browser

  • mobile-device or tablet-device`

  • ios-platform or windows-platform

  • touch-device (for touchscreens)

  • viewport-mobile or viewport-desktop (for screen sizes)

πŸ’‘ Why It’s Useful

These classes let you:

  • Create browser-specific styles when needed

  • Fix compatibility issues for certain browsers

  • Enhance mobile experiences with targeted CSS

  • Adapt layouts based on device capabilities

  • Implement responsive design with JavaScript support

πŸ“ How To Use The Classes

Once the script is running, you can write CSS like this:

css
/* Make buttons larger on touch devices */
.touch-device .button {
    padding: 12px 24px;
    font-size: 18px;
}

/* Fix a Safari-specific issue */
.safari-browser .problem-element {
    transform: translateZ(0);
}

/* Adjust layout for mobile */
.mobile-device .sidebar {
    display: none;
}
css
/* Fix for Safari flexbox gap issues */
.safari-browser .card-grid {
    gap: 0;
}

.safari-browser .card-grid > .card {
    margin: 10px;
}

Here’s the complete browser detection utility:

javascript
/**
 * Enhanced Browser Detection Utility
 *
 * This script identifies the user's browser and applies appropriate classes to the document body.
 * It also provides responsive class handling for different screen sizes.
 *
 * Features:
 * - Detects major browsers (Safari, Firefox, Chrome, Edge, Opera)
 * - Detects mobile browsers and applies appropriate classes
 * - Applies browser-specific classes to body element
 * - Adds responsive classes for different screen sizes
 * - Provides feature detection for more reliable browser identification
 * - Handles edge cases and errors gracefully
 * - Uses a configurable, efficient event handling system
 * - Includes memory management to prevent leaks
 */
document.addEventListener("DOMContentLoaded", function () {
    const BrowserDetector = {
        /**
         * Configuration options with defaults
         */
        config: {
            resizeDebounceTime: 200,
            breakpoints: {
                mobile: 480,
                tablet: 768,
                desktop: 1025,
                largeDesktop: 1440,
            },
            enableLogging: false,
            enableFeatureDetection: true,
            fallbackBrowserClass: "unknown-browser",
        },

        /**
         * Current detected browser and platform state
         */
        state: {
            currentBrowser: null,
            isMobile: false,
            isTouch: false,
            isPrivate: false,
            isLegacy: false,
            initialized: false,
        },

        /**
         * Browser detection methods
         * Each returns boolean indicating if the browser matches
         */
        browsers: {
            safari: function () {
                // Enhanced Safari detection
                const vendor = navigator.vendor || "";
                // Regular Safari
                const isSafari =
                    vendor.includes("Apple") &&
                    !navigator.userAgent.includes("Chrome") &&
                    !navigator.userAgent.includes("Edg");

                // Safari on iOS
                const isSafariIOS =
                    /^((?!chrome|android).)*safari/i.test(
                        navigator.userAgent
                    ) ||
                    (vendor.indexOf("Apple") > -1 &&
                        navigator.userAgent.indexOf("CriOS") === -1 &&
                        navigator.userAgent.indexOf("FxiOS") === -1);

                return isSafari || isSafariIOS;
            },
            firefox: function () {
                return (
                    navigator.userAgent.includes("Firefox") ||
                    navigator.userAgent.includes("FxiOS")
                );
            },
            chrome: function () {
                const vendor = navigator.vendor || "";
                // Regular Chrome
                const isChrome =
                    navigator.userAgent.includes("Chrome") &&
                    vendor.includes("Google") &&
                    !navigator.userAgent.includes("Edg");

                // Chrome on iOS
                const isChromeIOS = navigator.userAgent.includes("CriOS");

                return isChrome || isChromeIOS;
            },
            edge: function () {
                return (
                    navigator.userAgent.includes("Edg") ||
                    navigator.userAgent.includes("Edge")
                );
            },
            opera: function () {
                return (
                    navigator.userAgent.includes("OPR") ||
                    navigator.userAgent.includes("Opera")
                );
            },
            ie: function () {
                const isIE = /*@cc_on!@*/ false || !!document.documentMode;
                const isIECompatMode =
                    navigator.userAgent.includes("Trident") ||
                    navigator.userAgent.includes("MSIE");
                return isIE || isIECompatMode;
            },
        },

        /**
         * Feature detection methods for more reliable browser checks
         */
        featureDetection: {
            isTouch: function () {
                return (
                    "ontouchstart" in window ||
                    navigator.maxTouchPoints > 0 ||
                    navigator.msMaxTouchPoints > 0
                );
            },
            isWebkit: function () {
                try {
                    return "WebkitAppearance" in document.documentElement.style;
                } catch (e) {
                    return false;
                }
            },
            isGecko: function () {
                try {
                    return "MozAppearance" in document.documentElement.style;
                } catch (e) {
                    return false;
                }
            },
            isBlink: function () {
                // A simple test for Blink rendering engine
                return (
                    (!!window.chrome || !!window.Intl) &&
                    !!CSS &&
                    "function" === typeof CSS.supports &&
                    CSS.supports("(--foo: red)") &&
                    !this.isGecko()
                );
            },
        },

        /**
         * Platform detection methods
         */
        platforms: {
            isMobilePlatform: function () {
                return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
                    navigator.userAgent
                );
            },
            isTabletPlatform: function () {
                const userAgent = navigator.userAgent.toLowerCase();
                return /(ipad|tablet|playbook|silk)|(android(?!.*mobile))/i.test(
                    userAgent
                );
            },
            isIOS: function () {
                return (
                    /iPad|iPhone|iPod/.test(navigator.userAgent) &&
                    !window.MSStream
                );
            },
            isAndroid: function () {
                return /Android/i.test(navigator.userAgent);
            },
            isMacOS: function () {
                return /Mac/.test(navigator.platform);
            },
            isWindows: function () {
                return /Win/.test(navigator.platform);
            },
            isLinux: function () {
                return /Linux/.test(navigator.platform);
            },
        },

        /**
         * Advanced detection methods for edge cases
         */
        advanced: {
            isLegacyBrowser: function () {
                // Check for IE or other legacy browsers
                const isIE = /*@cc_on!@*/ false || !!document.documentMode;
                const isOldEdge = /Edge\/\d./i.test(navigator.userAgent);
                const isOldFirefox =
                    /Firefox\/(\d+)\./.test(navigator.userAgent) &&
                    parseInt(RegExp.$1, 10) < 40;
                const isOldChrome =
                    /Chrome\/(\d+)\./.test(navigator.userAgent) &&
                    parseInt(RegExp.$1, 10) < 40;

                return isIE || isOldEdge || isOldFirefox || isOldChrome;
            },
            tryDetectPrivateMode: function () {
                // This is a best effort as private browsing detection is complex
                // and browsers actively work to prevent reliable detection
                try {
                    const testKey = "browserDetectionTest";
                    localStorage.setItem(testKey, "1");
                    localStorage.removeItem(testKey);
                    return false;
                } catch (e) {
                    return true;
                }
            },
        },

        /**
         * Utility methods for DOM and helper functions
         */
        utils: {
            debounce: function (func, wait) {
                let timeout;
                return function (...args) {
                    const context = this;
                    clearTimeout(timeout);
                    timeout = setTimeout(() => func.apply(context, args), wait);
                };
            },
            addClass: function (className) {
                if (className && typeof className === "string") {
                    document.body.classList.add(className);
                }
            },
            removeClass: function (className) {
                if (className && typeof className === "string") {
                    document.body.classList.remove(className);
                }
            },
            toggleClass: function (className, condition) {
                if (className && typeof className === "string") {
                    document.body.classList.toggle(className, !!condition);
                }
            },
            log: function (message, data) {
                if (BrowserDetector.config.enableLogging) {
                    console.log(`[BrowserDetector] ${message}`, data || "");
                }
            },
            error: function (message, error) {
                console.error(`[BrowserDetector] ${message}`, error || "");
            },
            cleanClassName: function (name) {
                return name.replace(/[^a-zA-Z0-9-_]/g, "").toLowerCase();
            },
        },

        /**
         * Detect current browser and set appropriate classes
         * Returns detected browser or 'unknown'
         */
        detectBrowser: function () {
            try {
                // Remove previous browser classes
                document.body.className = document.body.className
                    .split(" ")
                    .filter((cls) => !cls.endsWith("-browser"))
                    .join(" ");

                // Test each browser and apply when found
                for (const [browser, detectFn] of Object.entries(
                    this.browsers
                )) {
                    if (detectFn()) {
                        const cleanName = this.utils.cleanClassName(browser);
                        this.utils.addClass(`${cleanName}-browser`);
                        this.state.currentBrowser = cleanName;
                        this.utils.log(`Detected browser: ${cleanName}`);
                        return cleanName;
                    }
                }

                // Fallback for unknown browsers
                this.utils.addClass(this.config.fallbackBrowserClass);
                this.state.currentBrowser = "unknown";
                this.utils.log("Unknown browser detected");
                return "unknown";
            } catch (error) {
                this.utils.error("Error detecting browser", error);
                this.utils.addClass("detection-error");
                this.utils.addClass(this.config.fallbackBrowserClass);
                this.state.currentBrowser = "unknown";
                return "unknown";
            }
        },

        /**
         * Detect platform features and set relevant classes
         */
        detectPlatformFeatures: function () {
            try {
                // Detect and set mobile/tablet status
                this.state.isMobile = this.platforms.isMobilePlatform();
                this.state.isTablet = this.platforms.isTabletPlatform();
                this.utils.toggleClass("mobile-device", this.state.isMobile);
                this.utils.toggleClass("tablet-device", this.state.isTablet);

                // Detect and set OS status
                if (this.platforms.isIOS()) {
                    this.utils.addClass("ios-platform");
                } else if (this.platforms.isAndroid()) {
                    this.utils.addClass("android-platform");
                } else if (this.platforms.isMacOS()) {
                    this.utils.addClass("macos-platform");
                } else if (this.platforms.isWindows()) {
                    this.utils.addClass("windows-platform");
                } else if (this.platforms.isLinux()) {
                    this.utils.addClass("linux-platform");
                }

                // Detect touch capability
                this.state.isTouch = this.featureDetection.isTouch();
                this.utils.toggleClass("touch-device", this.state.isTouch);

                // Legacy browser detection
                this.state.isLegacy = this.advanced.isLegacyBrowser();
                this.utils.toggleClass("legacy-browser", this.state.isLegacy);

                // Private browsing (best effort)
                if (this.config.detectPrivateBrowsing) {
                    this.state.isPrivate = this.advanced.tryDetectPrivateMode();
                    this.utils.toggleClass(
                        "private-browsing",
                        this.state.isPrivate
                    );
                }

                this.utils.log("Platform features detected", this.state);
            } catch (error) {
                this.utils.error("Error detecting platform features", error);
                this.utils.addClass("feature-detection-error");
            }
        },

        /**
         * Handle responsive classes based on window size
         */
        handleResponsive: function () {
            try {
                const width = window.innerWidth;
                const { breakpoints } = this.config;
                const currentBrowser = this.state.currentBrowser;

                if (!currentBrowser) {
                    this.utils.error(
                        "Cannot set responsive classes: browser not detected"
                    );
                    return;
                }

                // Remove previous responsive classes
                for (const size of Object.keys(breakpoints)) {
                    this.utils.removeClass(`${currentBrowser}-${size}`);
                }

                // Add appropriate responsive class
                if (width < breakpoints.mobile) {
                    this.utils.addClass(`${currentBrowser}-mobile`);
                    this.utils.addClass("viewport-mobile");
                } else if (width < breakpoints.tablet) {
                    this.utils.addClass(`${currentBrowser}-tablet`);
                    this.utils.addClass("viewport-tablet");
                } else if (width < breakpoints.desktop) {
                    this.utils.addClass(`${currentBrowser}-tablet-large`);
                    this.utils.addClass("viewport-tablet-large");
                } else if (width < breakpoints.largeDesktop) {
                    this.utils.addClass(`${currentBrowser}-desktop`);
                    this.utils.addClass("viewport-desktop");
                } else {
                    this.utils.addClass(`${currentBrowser}-desktop-large`);
                    this.utils.addClass("viewport-desktop-large");
                }

                this.utils.log(
                    `Responsive classes updated for width: ${width}px`
                );
            } catch (error) {
                this.utils.error("Error handling responsive classes", error);
                this.utils.addClass("responsive-error");
            }
        },

        /**
         * Validate and normalize configuration
         */
        validateConfig: function (customConfig) {
            try {
                // Merge custom config with defaults
                if (customConfig && typeof customConfig === "object") {
                    this.config = { ...this.config, ...customConfig };
                }

                // Ensure breakpoints are in ascending order
                const { breakpoints } = this.config;
                if (
                    breakpoints.mobile > breakpoints.tablet ||
                    breakpoints.tablet > breakpoints.desktop ||
                    breakpoints.desktop > breakpoints.largeDesktop
                ) {
                    this.utils.error(
                        "Invalid breakpoints configuration: values must be in ascending order"
                    );
                    // Reset to defaults
                    this.config.breakpoints = {
                        mobile: 480,
                        tablet: 768,
                        desktop: 1025,
                        largeDesktop: 1440,
                    };
                }

                // Ensure debounce time is reasonable
                if (
                    typeof this.config.resizeDebounceTime !== "number" ||
                    this.config.resizeDebounceTime < 50 ||
                    this.config.resizeDebounceTime > 1000
                ) {
                    this.config.resizeDebounceTime = 200; // Reset to default
                }

                this.utils.log("Configuration validated", this.config);
            } catch (error) {
                this.utils.error("Error validating configuration", error);
                // Reset to defaults
                this.config = {
                    resizeDebounceTime: 200,
                    breakpoints: {
                        mobile: 480,
                        tablet: 768,
                        desktop: 1025,
                        largeDesktop: 1440,
                    },
                    enableLogging: false,
                    enableFeatureDetection: true,
                    fallbackBrowserClass: "unknown-browser",
                };
            }
        },

        /**
         * Initialize the detector with optional custom configuration
         */
        init: function (customConfig) {
            try {
                if (this.state.initialized) {
                    this.utils.log(
                        "Already initialized; call destroy() first to reinitialize"
                    );
                    return this.state.currentBrowser;
                }

                this.utils.log("Initializing Browser Detector");
                this.validateConfig(customConfig);

                // Add basic initial class
                this.utils.addClass("js-enabled");

                // Run detection processes
                this.detectBrowser();

                if (this.config.enableFeatureDetection) {
                    this.detectPlatformFeatures();
                }

                this.handleResponsive();

                // Set up resize listener with debouncing
                const debouncedResize = this.utils.debounce(
                    this.handleResponsive.bind(this),
                    this.config.resizeDebounceTime
                );

                // Store reference for cleanup
                this._resizeHandler = debouncedResize;
                window.addEventListener("resize", debouncedResize);

                // Mark as initialized
                this.state.initialized = true;

                // Add version class
                this.utils.addClass("browser-detection-active");

                this.utils.log("Browser Detector initialized", {
                    browser: this.state.currentBrowser,
                    state: this.state,
                });

                return this.state.currentBrowser;
            } catch (error) {
                this.utils.error(
                    "Failed to initialize Browser Detector",
                    error
                );
                this.utils.addClass("initialization-error");
                return "unknown";
            }
        },

        /**
         * Clean up resources and event listeners
         */
        destroy: function () {
            try {
                if (!this.state.initialized) {
                    this.utils.log("Not initialized; nothing to destroy");
                    return false;
                }

                this.utils.log("Destroying Browser Detector");

                // Remove resize listener
                if (this._resizeHandler) {
                    window.removeEventListener("resize", this._resizeHandler);
                    this._resizeHandler = null;
                }

                // Remove status classes
                this.utils.removeClass("browser-detection-active");
                this.utils.removeClass("js-enabled");

                // Reset state
                this.state.initialized = false;
                this.state.currentBrowser = null;

                this.utils.log("Browser Detector destroyed");
                return true;
            } catch (error) {
                this.utils.error("Error destroying Browser Detector", error);
                return false;
            }
        },

        /**
         * Public API to manually refresh detection
         */
        refresh: function () {
            if (!this.state.initialized) {
                this.utils.error("Cannot refresh: not initialized");
                return false;
            }

            this.utils.log("Manually refreshing detection");
            this.detectBrowser();

            if (this.config.enableFeatureDetection) {
                this.detectPlatformFeatures();
            }

            this.handleResponsive();
            return true;
        },

        /**
         * Public API to get current detection state
         */
        getState: function () {
            return { ...this.state };
        },
    };

    // Initialize the detector and expose globally
    const currentBrowser = BrowserDetector.init();
    window.BrowserDetector = BrowserDetector;

    // Log initial detection results
    if (BrowserDetector.config.enableLogging) {
        console.log("[BrowserDetector] Detected:", {
            browser: currentBrowser,
            isMobile: BrowserDetector.state.isMobile,
            isTouch: BrowserDetector.state.isTouch,
        });
    }
});

🧩 Key Components

The code consists of several modules:

  • Browser detection methods

  • Platform detection functions

  • Feature detection capabilities

  • Responsive handling for different screen sizes

  • Utility functions for managing CSS classes

βš™οΈ Configuration Options

  • You can customize the script with options like:

  • Resize debounce time (how quickly it responds to window resizing)

  • Custom breakpoints for different screen sizes

  • Enabling/disabling certain detection features

  • Turning on logging for debugging

πŸ“± Device Detection

The code specifically checks for:

  • Mobile devices (phones)

  • Tablets

  • Touch capabilities

  • Operating systems

  • Legacy browser support

πŸ”„ Responsive Features

When a user resizes their browser:

  • The script detects the new size

  • Updates classes accordingly

  • Applies appropriate viewport classes

  • Does this efficiently with debouncing

πŸ“‹ How To Implement

Add this script to your website, and it will automatically:

  • Run when the page loads

  • Detect all browser/device information

  • Add the appropriate classes

  • Set up listeners for window resizing

  • Make the detector available as window.BrowserDetector

Now your website can intelligently adapt to different browsing environments!


Simplified Version

If you don’t need all the advanced features and just want basic browser detection with responsive classes, here’s a lighter alternative version:

javascript
/**
 * Browser Detection Utility
 *
 * This script identifies the user's browser and applies appropriate classes to the document body.
 * It also provides responsive class handling for different screen sizes.
 *
 * Features:
 * - Detects major browsers (Safari, Firefox, Chrome, Edge, Opera)
 * - Applies browser-specific classes to body element
 * - Adds responsive classes for different screen sizes
 * - Uses a configurable, efficient event handling system
 */
document.addEventListener("DOMContentLoaded", function () {
    const BrowserDetector = {
        config: {
            resizeDebounceTime: 200,
            breakpoints: {
                mobile: 480,
                tablet: 768,
                desktop: 1025,
                largeDesktop: 1440,
            },
        },

        browsers: {
            safari: function () {
                const vendor = navigator.vendor || "";
                return (
                    vendor.includes("Apple") &&
                    !navigator.userAgent.includes("Chrome") &&
                    !navigator.userAgent.includes("Edg") &&
                    ("ontouchend" in document || window.safari !== undefined)
                );
            },
            firefox: function () {
                return navigator.userAgent.includes("Firefox");
            },
            chrome: function () {
                const vendor = navigator.vendor || "";
                return (
                    navigator.userAgent.includes("Chrome") &&
                    vendor.includes("Google") &&
                    !navigator.userAgent.includes("Edg")
                );
            },
            edge: function () {
                return navigator.userAgent.includes("Edg");
            },
            opera: function () {
                return (
                    navigator.userAgent.includes("OPR") ||
                    navigator.userAgent.includes("Opera")
                );
            },
        },

        utils: {
            debounce: function (func, wait) {
                let timeout;
                return function (...args) {
                    clearTimeout(timeout);
                    timeout = setTimeout(() => func.apply(this, args), wait);
                };
            },
            addClass: function (className) {
                document.body.classList.add(className);
            },
            removeClass: function (className) {
                document.body.classList.remove(className);
            },
            toggleClass: function (className, condition) {
                document.body.classList.toggle(className, condition);
            },
        },

        detectBrowser: function () {
            document.body.className = document.body.className
                .split(" ")
                .filter((cls) => !cls.endsWith("-browser"))
                .join(" ");

            for (const [browser, detectFn] of Object.entries(this.browsers)) {
                if (detectFn()) {
                    this.utils.addClass(`${browser}-browser`);
                    this.currentBrowser = browser;
                    return browser;
                }
            }

            this.utils.addClass("unknown-browser");
            this.currentBrowser = "unknown";
            return "unknown";
        },

        handleResponsive: function () {
            const width = window.innerWidth;
            const { breakpoints } = this.config;

            for (const size of Object.keys(breakpoints)) {
                this.utils.removeClass(`${this.currentBrowser}-${size}`);
            }

            if (width < breakpoints.mobile) {
                this.utils.addClass(`${this.currentBrowser}-mobile`);
            } else if (width < breakpoints.tablet) {
                this.utils.addClass(`${this.currentBrowser}-tablet`);
            } else if (width < breakpoints.desktop) {
                this.utils.addClass(`${this.currentBrowser}-tablet-large`);
            } else if (width < breakpoints.largeDesktop) {
                this.utils.addClass(`${this.currentBrowser}-desktop`);
            } else {
                this.utils.addClass(`${this.currentBrowser}-desktop-large`);
            }
        },

        init: function () {
            this.detectBrowser();
            this.handleResponsive();
            window.addEventListener(
                "resize",
                this.utils.debounce(
                    this.handleResponsive.bind(this),
                    this.config.resizeDebounceTime
                )
            );
            return this.currentBrowser;
        },
    };

    const currentBrowser = BrowserDetector.init();
    window.BrowserDetector = BrowserDetector;
});

This simplified version retains the core functionality while removing some advanced features. It still detects the browser and applies responsive classes based on screen size.

What’s Different in the Simplified Version?

The simplified version:

  1. Is about 75% smaller (only ~120 lines vs. ~500 lines)

  2. Focuses on the core browser detection functionality

  3. Still handles responsive classes based on screen size

  4. Omits advanced features like:

    • Mobile and tablet detection

    • OS detection

    • Touch capability detection

    • Legacy browser detection

    • Private browsing detection

    • Extensive error handling

    • Feature detection for browser identification

    • Configuration validation

This lightweight version is ideal for blogs or websites that just need basic browser detection without all the bells and whistles.

Example Usage

The simplified version still provides the same core browser classes:

css
/**
 * Browser-specific styling
 *
 * This SCSS handles browser-specific adjustments for various layouts and components
 * based on the classes applied by the BrowserDetector script.
 */

body {
    &.safari-browser {
        &.safari-desktop {
            /*  Safari on desktop devices */
        }

        &.safari-tablet,
        &.safari-mobile {
            /*  Safari on mobile and tablet devices */
        }
    }

    /*  Firefox-specific adjustments */
    &.firefox-browser {
        &.firefox-mobile {
            /* Add mobile Firefox styles here */
        }
        &.firefox-desktop {
            /* Add desktop Firefox styles here */
        }
    }

    /* Chrome-specific adjustments */
    &.chrome-browser {
        /* Add any Chrome-specific styles here */
    }

    /* Edge-specific adjustments */
    &.edge-browser {
        /* Add any Edge-specific styles here */
    }

    /* Opera-specific adjustments */
    &.opera-browser {
        /*  Add any Opera-specific styles here */
    }
}