Navigator 与 Screen 对象:浏览器与屏幕信息侦探
双侦探的分工
在 BOM 的世界里,navigator 和 screen 就像是两位专业的信息侦探。navigator 专门收集浏览器和系统的信息——告诉你"用户在用什么浏览器、什么操作系统、是否在线";而 screen 则专注于屏幕硬件信息——告诉你"用户的屏幕有多大、支持多少种颜色、分辨率是多少"。
理解这两个对象的区别很重要:
- Navigator:关注浏览器软件环境(浏览器类型、版本、插件、语言等)
- Screen:关注显示硬件环境(屏幕尺寸、颜色深度、方向等)
两者配合使用,可以帮助我们实现智能的设备适配和功能检测。
Navigator 对象详解
navigator 对象包含浏览器和操作系统的各种信息,是进行特性检测和用户环境分析的重要工具。
浏览器识别属性
javascript
// 用户代理字符串(最常用的浏览器识别方式)
console.log(navigator.userAgent);
// 例如: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
// 应用程序名称(通常是 'Netscape',由于历史原因)
console.log(navigator.appName);
// 应用程序版本
console.log(navigator.appVersion);
// 操作系统平台
console.log(navigator.platform);
// 例如: 'Win32', 'MacIntel', 'Linux x86_64'
// 浏览器供应商
console.log(navigator.vendor);
// 例如: 'Google Inc.', 'Apple Computer, Inc.'
// 浏览器引擎名称
console.log(navigator.product);
// 通常是 'Gecko'重要提示:由于userAgent 可以被伪造,现代最佳实践是使用特性检测而非浏览器检测。
实际应用:浏览器检测工具
javascript
class BrowserDetector {
static get userAgent() {
return navigator.userAgent.toLowerCase();
}
// 检测浏览器类型
static getBrowserName() {
const ua = this.userAgent;
if (ua.includes("edg/")) return "Edge";
if (ua.includes("chrome/") && !ua.includes("edg/")) return "Chrome";
if (ua.includes("safari/") && !ua.includes("chrome/")) return "Safari";
if (ua.includes("firefox/")) return "Firefox";
if (ua.includes("opera/") || ua.includes("opr/")) return "Opera";
if (ua.includes("trident/") || ua.includes("msie "))
return "Internet Explorer";
return "Unknown";
}
// 检测浏览器版本
static getBrowserVersion() {
const ua = this.userAgent;
const browser = this.getBrowserName();
let match;
switch (browser) {
case "Chrome":
match = ua.match(/chrome\/([\d.]+)/);
break;
case "Safari":
match = ua.match(/version\/([\d.]+)/);
break;
case "Firefox":
match = ua.match(/firefox\/([\d.]+)/);
break;
case "Edge":
match = ua.match(/edg\/([\d.]+)/);
break;
default:
return "Unknown";
}
return match ? match[1] : "Unknown";
}
// 检测操作系统
static getOS() {
const ua = this.userAgent;
const platform = navigator.platform.toLowerCase();
if (platform.includes("win")) return "Windows";
if (platform.includes("mac")) return "macOS";
if (platform.includes("linux")) return "Linux";
if (ua.includes("android")) return "Android";
if (ua.includes("iphone") || ua.includes("ipad")) return "iOS";
return "Unknown";
}
// 检测设备类型
static getDeviceType() {
const ua = this.userAgent;
if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
return "tablet";
}
if (
/Mobile|Android|iP(hone|od)|IEMobile|BlackBerry|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(
ua
)
) {
return "mobile";
}
return "desktop";
}
// 获取完整信息
static getInfo() {
return {
browser: this.getBrowserName(),
version: this.getBrowserVersion(),
os: this.getOS(),
deviceType: this.getDeviceType(),
platform: navigator.platform,
userAgent: navigator.userAgent,
};
}
// 显示浏览器信息
static displayInfo() {
const info = this.getInfo();
console.log("=== 浏览器信息 ===");
console.log(`浏览器: ${info.browser} ${info.version}`);
console.log(`操作系统: ${info.os}`);
console.log(`设备类型: ${info.deviceType}`);
console.log(`平台信息: ${info.platform}`);
}
}
// 使用
BrowserDetector.displayInfo();
// 输出:
// === 浏览器信息 ===
// 浏览器: Chrome 120.0.0.0
// 操作系统: Windows
// 设备类型: desktop
// 平台信息: Win32语言与国际化
javascript
// 浏览器首选语言
console.log(navigator.language);
// 例如: 'zh-CN', 'en-US', 'ja-JP'
// 所有支持的语言
console.log(navigator.languages);
// 例如: ['zh-CN', 'zh', 'en']
// 实际应用:自动语言检测
class LanguageDetector {
static getSupportedLanguages() {
return ["en", "zh", "ja", "es", "fr", "de"];
}
static detectLanguage() {
const userLang = navigator.language || navigator.languages[0];
const langCode = userLang.split("-")[0]; // 'zh-CN' -> 'zh'
const supported = this.getSupportedLanguages();
if (supported.includes(langCode)) {
return langCode;
}
return "en"; // 默认语言
}
static setPageLanguage() {
const lang = this.detectLanguage();
// 设置 HTML 语言属性
document.documentElement.lang = lang;
// 加载对应的语言包
console.log(`检测到语言: ${lang}`);
// 实际应用中,可以加载对应的语言文件
// this.loadLanguagePack(lang);
return lang;
}
static async loadLanguagePack(lang) {
try {
const response = await fetch(`/i18n/${lang}.json`);
const translations = await response.json();
console.log(`加载语言包: ${lang}`, translations);
return translations;
} catch (error) {
console.error(`加载语言包失败: ${lang}`, error);
return {};
}
}
}
// 使用
const language = LanguageDetector.setPageLanguage();
console.log("页面语言:", language);在线状态检测
javascript
// 检测浏览器当前的在线状态
console.log("在线状态:", navigator.onLine); // true 或 false
// 监听在线/离线事件
window.addEventListener("online", () => {
console.log("网络已连接");
document.body.classList.remove("offline");
document.body.classList.add("online");
});
window.addEventListener("offline", () => {
console.log("网络已断开");
document.body.classList.remove("online");
document.body.classList.add("offline");
});
// 实际应用:网络状态管理器
class NetworkManager {
constructor() {
this.listeners = [];
this.init();
}
init() {
window.addEventListener("online", () => this.handleOnline());
window.addEventListener("offline", () => this.handleOffline());
// 初始状态
if (!navigator.onLine) {
this.handleOffline();
}
}
handleOnline() {
console.log("网络已恢复");
this.showNotification("网络已连接", "success");
this.syncOfflineData();
this.notifyListeners("online");
}
handleOffline() {
console.log("网络已断开");
this.showNotification("网络已断开,部分功能可能不可用", "warning");
this.enableOfflineMode();
this.notifyListeners("offline");
}
showNotification(message, type = "info") {
console.log(`[${type.toUpperCase()}] ${message}`);
// 实际应用中,显示一个友好的通知UI
/*
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => notification.remove(), 3000);
*/
}
enableOfflineMode() {
console.log("启用离线模式");
// 禁用需要网络的功能
const onlineOnlyButtons = document.querySelectorAll("[data-online-only]");
onlineOnlyButtons.forEach((btn) => {
btn.disabled = true;
btn.title = "此功能需要网络连接";
});
}
async syncOfflineData() {
console.log("同步离线数据...");
// 实际应用中,从 IndexedDB 或 localStorage 获取离线数据并同步
const offlineData = this.getOfflineData();
if (offlineData.length > 0) {
try {
// 同步数据到服务器
// await fetch('/api/sync', {
// method: 'POST',
// body: JSON.stringify(offlineData)
// });
console.log("离线数据同步完成");
this.clearOfflineData();
} catch (error) {
console.error("同步失败:", error);
}
}
}
getOfflineData() {
// 从本地存储获取离线数据
return []; // 简化示例
}
clearOfflineData() {
// 清除已同步的离线数据
}
onStatusChange(callback) {
this.listeners.push(callback);
}
notifyListeners(status) {
this.listeners.forEach((callback) => callback(status));
}
isOnline() {
return navigator.onLine;
}
}
// 使用
const networkManager = new NetworkManager();
networkManager.onStatusChange((status) => {
console.log("网络状态变化:", status);
});Cookie 与存储
javascript
// 检测 Cookie 是否启用
console.log("Cookie 已启用:", navigator.cookieEnabled);
if (!navigator.cookieEnabled) {
alert("请启用 Cookie 以获得最佳体验");
}
// 检测 Do Not Track 设置
console.log("Do Not Track:", navigator.doNotTrack);
// '1' = 启用, '0' = 未启用, null = 未设置地理位置 API
javascript
// 检测地理位置 API 是否可用
if ("geolocation" in navigator) {
console.log("支持地理位置 API");
// 获取当前位置
navigator.geolocation.getCurrentPosition(
(position) => {
console.log("位置信息:");
console.log(" 纬度:", position.coords.latitude);
console.log(" 经度:", position.coords.longitude);
console.log(" 精度:", position.coords.accuracy, "米");
console.log(" 海拔:", position.coords.altitude);
console.log(" 时间戳:", new Date(position.timestamp));
},
(error) => {
console.error("获取位置失败:", error.message);
switch (error.code) {
case error.PERMISSION_DENIED:
console.log("用户拒绝了地理位置请求");
break;
case error.POSITION_UNAVAILABLE:
console.log("位置信息不可用");
break;
case error.TIMEOUT:
console.log("请求超时");
break;
}
},
{
enableHighAccuracy: true, // 高精度模式
timeout: 5000, // 超时时间(毫秒)
maximumAge: 0, // 缓存时间(0 = 不使用缓存)
}
);
} else {
console.log("不支持地理位置 API");
}
// 实际应用:地理位置管理器
class GeolocationManager {
static async getCurrentPosition() {
return new Promise((resolve, reject) => {
if (!("geolocation" in navigator)) {
reject(new Error("浏览器不支持地理位置 API"));
return;
}
navigator.geolocation.getCurrentPosition(
(position) => {
resolve({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: position.timestamp,
});
},
(error) => {
reject(error);
},
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 60000, // 缓存 1 分钟
}
);
});
}
static watchPosition(callback) {
if (!("geolocation" in navigator)) {
console.error("浏览器不支持地理位置 API");
return null;
}
const watchId = navigator.geolocation.watchPosition(
(position) => {
callback({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
timestamp: position.timestamp,
});
},
(error) => {
console.error("位置监听错误:", error);
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0,
}
);
return watchId;
}
static stopWatching(watchId) {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
}
}
static async getAddressFromCoords(lat, lng) {
// 实际应用中,调用地理编码 API
console.log(`获取地址: ${lat}, ${lng}`);
// 示例:使用 OpenStreetMap Nominatim API
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?lat=${lat}&lon=${lng}&format=json`
);
const data = await response.json();
return data.display_name;
} catch (error) {
console.error("获取地址失败:", error);
return null;
}
}
}
// 使用
async function showCurrentLocation() {
try {
const position = await GeolocationManager.getCurrentPosition();
console.log("当前位置:", position);
const address = await GeolocationManager.getAddressFromCoords(
position.latitude,
position.longitude
);
console.log("地址:", address);
} catch (error) {
console.error("获取位置失败:", error.message);
}
}
// 监听位置变化
// const watchId = GeolocationManager.watchPosition((position) => {
// console.log('位置更新:', position);
// });Screen 对象详解
screen 对象提供关于用户屏幕的各种信息,主要用于屏幕适配和显示优化。
屏幕尺寸属性
javascript
// 屏幕总尺寸(包括任务栏等)
console.log("屏幕宽度:", screen.width); // 例如: 1920
console.log("屏幕高度:", screen.height); // 例如: 1080
// 可用屏幕尺寸(排除任务栏等)
console.log("可用宽度:", screen.availWidth); // 例如: 1920
console.log("可用高度:", screen.availHeight); // 例如: 1040
// 计算任务栏占用的空间
const taskbarHeight = screen.height - screen.availHeight;
console.log("任务栏高度:", taskbarHeight); // 例如: 40
// 实际应用:检测屏幕信息
function getScreenInfo() {
return {
total: {
width: screen.width,
height: screen.height,
},
available: {
width: screen.availWidth,
height: screen.availHeight,
},
taskbar: {
height: screen.height - screen.availHeight,
width: screen.width - screen.availWidth,
},
aspectRatio: (screen.width / screen.height).toFixed(2),
};
}
console.log("屏幕信息:", getScreenInfo());颜色深度与像素深度
javascript
// 颜色深度(位数)
console.log("颜色深度:", screen.colorDepth);
// 通常是 24 (约 1670 万色)
// 像素深度(通常与颜色深度相同)
console.log("像素深度:", screen.pixelDepth);
// 实际应用:颜色支持检测
class ColorSupportDetector {
static getColorSupport() {
const depth = screen.colorDepth;
if (depth >= 24) {
return "truecolor"; // 真彩色(1670 万色以上)
} else if (depth >= 16) {
return "highcolor"; // 高彩色(65536 色)
} else if (depth >= 8) {
return "lowcolor"; // 256 色
} else {
return "monochrome"; // 单色
}
}
static canDisplay(colorMode) {
const support = this.getColorSupport();
const levels = ["monochrome", "lowcolor", "highcolor", "truecolor"];
return levels.indexOf(support) >= levels.indexOf(colorMode);
}
static adjustImageQuality() {
const support = this.getColorSupport();
if (support === "truecolor") {
console.log("使用高质量图片");
return "high";
} else if (support === "highcolor") {
console.log("使用中等质量图片");
return "medium";
} else {
console.log("使用低质量图片");
return "low";
}
}
}
// 使用
console.log("颜色支持:", ColorSupportDetector.getColorSupport());
console.log("可以显示真彩色:", ColorSupportDetector.canDisplay("truecolor"));
const imageQuality = ColorSupportDetector.adjustImageQuality();屏幕方向
javascript
// 屏幕方向(现代浏览器)
if (screen.orientation) {
console.log("屏幕方向:", screen.orientation.type);
// 'portrait-primary', 'portrait-secondary', 'landscape-primary', 'landscape-secondary'
console.log("旋转角度:", screen.orientation.angle);
// 0, 90, 180, 270
// 监听方向变化
screen.orientation.addEventListener("change", () => {
console.log("方向已改变:", screen.orientation.type);
console.log("旋转角度:", screen.orientation.angle);
});
}
// 实际应用:屏幕方向管理器
class OrientationManager {
constructor() {
this.listeners = [];
this.init();
}
init() {
if (!screen.orientation) {
console.warn("浏览器不支持 screen.orientation API");
return;
}
screen.orientation.addEventListener("change", () => {
this.handleOrientationChange();
});
// 初始检测
this.handleOrientationChange();
}
handleOrientationChange() {
const orientation = this.getCurrentOrientation();
console.log("当前方向:", orientation);
// 更新 body 的 class
document.body.classList.remove("portrait", "landscape");
document.body.classList.add(orientation);
// 通知监听器
this.notifyListeners(orientation);
}
getCurrentOrientation() {
if (screen.orientation) {
return screen.orientation.type.includes("portrait")
? "portrait"
: "landscape";
}
// 降级方案:使用窗口尺寸判断
return window.innerHeight > window.innerWidth ? "portrait" : "landscape";
}
isPortrait() {
return this.getCurrentOrientation() === "portrait";
}
isLandscape() {
return this.getCurrentOrientation() === "landscape";
}
onChange(callback) {
this.listeners.push(callback);
}
notifyListeners(orientation) {
this.listeners.forEach((callback) => callback(orientation));
}
async lockOrientation(orientation) {
if (!screen.orientation || !screen.orientation.lock) {
console.warn("浏览器不支持锁定屏幕方向");
return false;
}
try {
await screen.orientation.lock(orientation);
console.log("屏幕方向已锁定:", orientation);
return true;
} catch (error) {
console.error("锁定方向失败:", error);
return false;
}
}
unlockOrientation() {
if (screen.orientation && screen.orientation.unlock) {
screen.orientation.unlock();
console.log("屏幕方向已解锁");
}
}
}
// 使用
const orientationManager = new OrientationManager();
orientationManager.onChange((orientation) => {
console.log("方向变化:", orientation);
if (orientation === "portrait") {
console.log("应用竖屏布局");
} else {
console.log("应用横屏布局");
}
});实际应用综合示例
设备适配管理器
javascript
class DeviceAdapter {
static getDeviceInfo() {
const browser = BrowserDetector.getInfo();
const screen = getScreenInfo();
return {
browser,
screen,
viewport: {
width: window.innerWidth,
height: window.innerHeight,
},
devicePixelRatio: window.devicePixelRatio || 1,
touchSupport: "ontouchstart" in window,
online: navigator.onLine,
language: navigator.language,
};
}
static getOptimalImageSize() {
const dpr = window.devicePixelRatio || 1;
const width = window.innerWidth;
// 根据DPR 和屏幕宽度选择合适的图片尺寸
if (dpr >= 3) {
return width < 768 ? "2x" : "3x";
} else if (dpr >= 2) {
return "2x";
} else {
return "1x";
}
}
static shouldLoadHighQualityAssets() {
// 综合考虑多个因素
const hasGoodConnection = navigator.connection
? navigator.connection.effectiveType === "4g"
: true;
const hasGoodScreen = screen.width >= 1920 && screen.colorDepth >= 24;
const hasGoodBrowser = !BrowserDetector.getBrowserName().includes("IE");
return hasGoodConnection && hasGoodScreen && hasGoodBrowser;
}
static applyOptimizations() {
const info = this.getDeviceInfo();
console.log("=== 设备适配 ===");
console.log("设备信息:", info);
// 根据设备能力应用优化
if (info.devicePixelRatio > 1) {
console.log("高DPI屏幕,加载 2x/3x 图片");
document.body.classList.add("retina");
}
if (!info.touchSupport) {
console.log("非触摸设备,启用hover效果");
document.body.classList.add("no-touch");
} else {
console.log("触摸设备,禁用hover效果");
document.body.classList.add("touch");
}
if (info.screen.total.width < 768) {
console.log("小屏幕设备,应用移动端优化");
document.body.classList.add("mobile-optimized");
}
if (this.shouldLoadHighQualityAssets()) {
console.log("设备能力强,加载高质量资源");
document.body.classList.add("high-quality");
} else {
console.log("设备能力有限,加载标准质量资源");
document.body.classList.add("standard-quality");
}
}
}
// 使用
DeviceAdapter.applyOptimizations();功能检测工具集
javascript
class FeatureDetector {
// 检测各种 API 支持
static checkSupport() {
return {
geolocation: "geolocation" in navigator,
localStorage: this.testLocalStorage(),
sessionStorage: this.testSessionStorage(),
indexedDB: "indexedDB" in window,
serviceWorker: "serviceWorker" in navigator,
webWorker: typeof Worker !== "undefined",
webSocket: "WebSocket" in window,
notification: "Notification" in window,
vibration: "vibrate" in navigator,
batteryAPI: "getBattery" in navigator,
networkInfo: "connection" in navigator,
mediaDevices: "mediaDevices" in navigator,
clipboard: "clipboard" in navigator,
share: "share" in navigator,
screenOrientation: "orientation" in screen,
};
}
static testLocalStorage() {
try {
const test = "__test__";
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
static testSessionStorage() {
try {
const test = "__test__";
sessionStorage.setItem(test, test);
sessionStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
static displaySupport() {
const support = this.checkSupport();
console.log("=== 浏览器功能支持 ===");
Object.entries(support).forEach(([feature, supported]) => {
const status = supported ? "✓" : "✗";
console.log(`${status} ${feature}`);
});
}
static getMissingFeatures(required) {
const support = this.checkSupport();
return required.filter((feature) => !support[feature]);
}
static checkRequirements(required) {
const missing = this.getMissingFeatures(required);
if (missing.length > 0) {
console.warn("缺少必需功能:", missing);
return false;
}
console.log("所有必需功能都支持");
return true;
}
}
// 使用
FeatureDetector.displaySupport();
// 检查特定功能需求
const requiredFeatures = ["geolocation", "localStorage", "serviceWorker"];
if (!FeatureDetector.checkRequirements(requiredFeatures)) {
alert("您的浏览器不支持某些必需功能,请升级浏览器");
}最佳实践
1. 使用特性检测而非浏览器检测
javascript
// ❌ 不推荐:浏览器检测
if (navigator.userAgent.includes("Chrome")) {
// 使用某个功能
}
// ✅ 推荐:特性检测
if ("geolocation" in navigator) {
// 使用地理位置功能
}
if (typeof Worker !== "undefined") {
// 使用 Web Worker
}2. 优雅降级
javascript
class StorageManager {
static set(key, value) {
if (FeatureDetector.testLocalStorage()) {
localStorage.setItem(key, value);
} else {
// 降级方案:使用 Cookie
document.cookie = `${key}=${value}; path=/`;
}
}
static get(key) {
if (FeatureDetector.testLocalStorage()) {
return localStorage.getItem(key);
} else {
// 从 Cookie 读取
const match = document.cookie.match(new RegExp(`${key}=([^;]+)`));
return match ? match[1] : null;
}
}
}3. 隐私保护
javascript
// 尊重用户的 Do Not Track 设置
if (navigator.doNotTrack === "1") {
console.log("用户启用了 Do Not Track,不收集统计数据");
// 禁用追踪功能
} else {
console.log("可以收集匿名统计数据");
// 启用追踪功能(需要用户同意)
}小结
navigator 和 screen 对象是获取浏览器和屏幕信息的重要工具:
Navigator 对象:
- 浏览器信息(
userAgent,appName,appVersion) - 系统信息(
platform,language) - 在线状态(
onLine) - 地理位置(
geolocation) - Cookie 支持(
cookieEnabled)
Screen 对象:
- 屏幕尺寸(
width,height,availWidth,availHeight) - 颜色深度(
colorDepth,pixelDepth) - 屏幕方向(
orientation)
最佳实践:
- 使用特性检测而非浏览器检测
- 实现优雅降级
- 尊重用户隐私设置
- 综合考虑多个因素进行设备适配
- 缓存检测结果以提高性能