Skip to content

Navigator 与 Screen 对象:浏览器与屏幕信息侦探

双侦探的分工

在 BOM 的世界里,navigatorscreen 就像是两位专业的信息侦探。navigator 专门收集浏览器和系统的信息——告诉你"用户在用什么浏览器、什么操作系统、是否在线";而 screen 则专注于屏幕硬件信息——告诉你"用户的屏幕有多大、支持多少种颜色、分辨率是多少"。

理解这两个对象的区别很重要:

  • Navigator:关注浏览器软件环境(浏览器类型、版本、插件、语言等)
  • Screen:关注显示硬件环境(屏幕尺寸、颜色深度、方向等)

两者配合使用,可以帮助我们实现智能的设备适配和功能检测。

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);
});
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("可以收集匿名统计数据");
  // 启用追踪功能(需要用户同意)
}

小结

navigatorscreen 对象是获取浏览器和屏幕信息的重要工具:

Navigator 对象:

  • 浏览器信息(userAgent, appName, appVersion
  • 系统信息(platform, language
  • 在线状态(onLine
  • 地理位置(geolocation
  • Cookie 支持(cookieEnabled

Screen 对象:

  • 屏幕尺寸(width, height, availWidth, availHeight
  • 颜色深度(colorDepth, pixelDepth
  • 屏幕方向(orientation

最佳实践:

  1. 使用特性检测而非浏览器检测
  2. 实现优雅降级
  3. 尊重用户隐私设置
  4. 综合考虑多个因素进行设备适配
  5. 缓存检测结果以提高性能