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:
-
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-browserorfirefox-browser -
mobile-deviceor tablet-device` -
ios-platformorwindows-platform -
touch-device(for touchscreens) -
viewport-mobileorviewport-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 */
}
}