JavaScript - Browser Detection Utility
This JavaScript utility helps your website identify and adapt to different browsers and devices. Here’s what it does in simple terms:
🔍 What This Code Does #
When someone visits your website, this script:
Identifies their browser (Chrome, Firefox, Safari, Edge, Opera)
Detects their device type (desktop, tablet, or mobile)
Recognizes their operating system (Windows, macOS, iOS, Android, Linux)
Checks screen size and adjusts when the window resizes
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
orfirefox-browser
mobile-device
or tablet-device`ios-platform
orwindows-platform
touch-device
(for touchscreens)viewport-mobile
orviewport-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:
/* 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;
}
/* 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:
/**
* 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:
/**
* 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:
Is about 75% smaller (only ~120 lines vs. ~500 lines)
Focuses on the core browser detection functionality
Still handles responsive classes based on screen size
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:
/**
* 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 */
}
}