浏览器藏了这么多神器,你居然不知道?

你以为浏览器只能打开网页?打开控制台敲 console.log?其实浏览器早就给你准备好了各种"神器"------只是 99% 的人从来没注意过。这些 API 之所以存在,是因为浏览器在发展过程中遇到了真实的问题。

今天,用工具箱的故事,来讲讲浏览器内置的 Web API。


原文地址

墨渊书肆/浏览器藏了这么多神器,你居然不知道?


浏览器是个工具箱

为什么浏览器要内置这么多 API?

浏览器就像一个万能工具箱

  • 木工要锤子、锯子、螺丝刀
  • 前端要检测元素、监控性能、访问硬件

浏览器面临的场景越来越复杂:

  • 检测页面何时可见/隐藏
  • 监听某个元素何时出现在屏幕
  • 监听 DOM 元素的大小变化
  • 监控网页性能数据
  • 访问电池、位置、剪贴板等硬件

所以浏览器厂商(Google、Mozilla、Apple)就把常见需求做成标准 API,让开发者直接用。

兼容性问题

在开始之前,先说个重要的事:不是所有 API 所有浏览器都支持

API Chrome Firefox Safari Edge
IntersectionObserver ✅ 51+ ✅ 55+ ✅ 12.1+ ✅ 79+
MutationObserver ✅ 18+ ✅ 14+ ✅ 6+ ✅ 12+
ResizeObserver ✅ 64+ ✅ 31+ ✅ 13.1+ ✅ 79+
PerformanceObserver ✅ 56+ ✅ 58+ ✅ 11+ ✅ 79+
Page Visibility API ✅ 33+ ✅ 18+ ✅ 6.1+ ✅ 12+
Battery API ✅ 50+ ✅ 10+ ❌ 不支持 ✅ 79+
Clipboard API ✅ 66+ ✅ 63+ ✅ 13.1+ ✅ 79+
Geolocation API ✅ 5+ ✅ 3.5+ ✅ 3+ ✅ 12+

注意:Battery API 在 Safari 和大多数移动浏览器上不支持,使用时要做好兼容处理。


IntersectionObserver --- 懒加载和无限滚动的救星

故事的起因:scroll 事件太慢了

十年前,前端要检测"某个元素是否出现在屏幕上",只能这样写:

javascript 复制代码
window.addEventListener('scroll', () => {
  const rect = element.getBoundingClientRect();
  if (rect.top < window.innerHeight) {
    loadImage();
  }
});

这段代码有什么问题?

问题 说明
性能差 scroll 事件每秒触发几十次,每次都计算 rect
无法批量 多个元素要写多个监听
滚动卡顿 计算太频繁,导致页面卡

就像门口站了个保安:每进来一个人,他都要站起来看一眼是不是 VIP------累死了。

IntersectionObserver 的原理

IntersectionObserver = 交叉观察器。

浏览器提供了这个 API,让你能高效地检测元素是否进入视口

原理很简单:

yaml 复制代码
IntersectionObserver 工作原理:
┌────────────────────────────┐
│       视口(Viewport)      │
│  ┌────────────────────┐    │
│  │     target 元素     │    │
│  │                    │    │
│  └────────────────────┘    │
└────────────────────────────┘
          ↓
   元素进入视口?浏览器自动通知你

基本用法

javascript 复制代码
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    console.log('元素可见性:', entry.isIntersecting);
    console.log('交叉比例:', entry.intersectionRatio);
  });
}, {
  root: null,              // null = 浏览器视口
  rootMargin: '0px',       // 扩大/缩小检测区域
  threshold: 0.5            // 50% 可见时触发
});

observer.observe(element);

配置参数详解

参数 作用 示例
root 参考视口 null=浏览器窗口,element=某个容器
rootMargin 视口的扩展区域 '100px'=提前100px就触发
threshold 触发时机 0=刚出现,0.5=一半可见,1=完全可见

实际应用:懒加载图片

这是最经典的应用场景:

html 复制代码
<img data-src="real-image.jpg" class="lazy" alt="加载中...">
<img data-src="real-image2.jpg" class="lazy" alt="加载中...">

<script>
  const images = document.querySelectorAll('.lazy');

  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        const img = entry.target;
        img.src = img.dataset.src;      // 加载真实图片
        img.classList.remove('lazy');    // 移除占位符样式
        observer.unobserve(img);         // 加载完停止观察
      }
    });
  }, {
    rootMargin: '100px'  // 提前 100px 开始加载
  });

  images.forEach(img => observer.observe(img));
</script>

实际应用:无限滚动

电商网站、社交媒体最常用的"加载更多":

javascript 复制代码
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      loadMoreItems().then(() => {
        // 新内容加载后,继续观察新的 loading 元素
        observer.observe(document.querySelector('.loading'));
      });
    }
  });
}, {
  rootMargin: '200px'  // 距离底部 200px 就开始加载
});

observer.observe(document.querySelector('.loading'));

MutationObserver --- DOM 变化探测器

故事的起因:jQuery 时代的噩梦

很久以前,前端要监听 DOM 变化是这样写的:

javascript 复制代码
$('#container').bind('DOMNodeInserted', function() {
  console.log('有新内容了');
});

后来有了 MutationEvent

javascript 复制代码
document.addEventListener('DOMNodeInserted', (e) => {
  console.log('有新节点:', e.target);
});

但这些事件有严重问题:

问题 说明
性能灾难 每次 DOM 变化都触发,变化多了直接卡死
不准确 不能区分是内容变了还是属性变了
已废弃 现代浏览器不再推荐使用

MutationObserver 的原理

MutationObserver = 变化观察器。

浏览器用它来高效地监听 DOM 变化,变化会被批量收集,一次性通知你。

yaml 复制代码
MutationObserver 原理:
┌──────────────────────────────┐
│           DOM 树             │
│    <div id="app">            │
│      <p>你好</p>   ← 变化     │
│    </div>                    │
└──────────────────────────────┘
          ↓
变化被收集 → 批量通知 → 一次回调

基本用法

javascript 复制代码
const observer = new MutationObserver((mutations) => {
  // mutations 数组包含所有变化
  mutations.forEach(mutation => {
    console.log('变化类型:', mutation.type);
    console.log('变化节点:', mutation.target);
  });
});

observer.observe(document.body, {
  childList: true,       // 监听子节点增删
  subtree: true,        // 监听所有后代节点
  attributes: true,     // 监听属性变化
  attributeOldValue: true,  // 记录变化前的属性值
  characterData: true,   // 监听文本变化
  characterDataOldValue: true  // 记录变化前的文本
});

变化类型详解

type 说明 包含内容
childList 子节点增删 新增或删除的节点
attributes 属性变化 变化的属性名和值
characterData 文本变化 变化前后的文本内容
yaml 复制代码
变化收集流程:
1. DOM 发生变化
2. 变化被记录到队列(不立即通知)
3. 变化积累一定数量或时间后
4. 批量通知观察者

实际应用:表单变化检测

监听表单是否有未保存的修改:

javascript 复制代码
const observer = new MutationObserver(() => {
  form.hasChanges = true;  // 标记有变化
});

observer.observe(form, {
  childList: true, subtree: true, attributes: true, characterData: true
});

form.addEventListener('submit', () => {
  form.hasChanges = false;
  observer.disconnect();
});

ResizeObserver --- 元素大小监听器

故事的起因:window.resize 太粗糙了

以前要监听元素大小变化,只能监听整个窗口:

javascript 复制代码
window.addEventListener('resize', () => {
  console.log('窗口大小:', window.innerWidth, window.innerHeight);
});

但这有两个问题:

问题 说明
不精确 只能监听窗口,不能监听某个 div
性能差 窗口每次 resize 都触发,频率很高

ResizeObserver 的原理

ResizeObserver = 大小观察器。

专门用来监听任意元素的大小变化,精准高效。

yaml 复制代码
ResizeObserver 原理:
┌─────────────────────────┐
│  <div class="box">      │
│    内容随着大小变化        │
│  </div>                 │
└─────────────────────────┘
          ↓
  大小变化 → 浏览器自动通知
          ↓
 entry.contentRect 包含新尺寸

基本用法

javascript 复制代码
const observer = new ResizeObserver((entries) => {
  entries.forEach(entry => {
    const { width, height } = entry.contentRect;
    console.log(`元素大小: ${width} x ${height}`);
  });
});

observer.observe(boxElement);

// 停止观察
observer.unobserve(boxElement);
observer.disconnect();

实际应用:响应式布局

根据容器大小切换布局:

javascript 复制代码
const observer = new ResizeObserver((entries) => {
  entries.forEach(entry => {
    const width = entry.contentRect.width;
    const target = entry.target;

    // 移除旧布局类
    target.classList.remove('mobile', 'tablet', 'desktop');

    // 根据宽度添加新布局类
    if (width < 600) {
      target.classList.add('mobile');
      renderMobileLayout(target);
    } else if (width < 1024) {
      target.classList.add('tablet');
      renderTabletLayout(target);
    } else {
      target.classList.add('desktop');
      renderDesktopLayout(target);
    }
  });
});

observer.observe(document.querySelector('.layout-container'));

PerformanceObserver --- 性能监控器

故事的起因:Performance API 太难用

浏览器原生提供 performance.timing 等 API 来获取性能数据:

javascript 复制代码
// 获取页面加载时间
const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart;
console.log('页面加载时间:', loadTime);

但问题是:

问题 说明
一次性 数据只在特定时间点有效
不实时 无法监控动态加载的资源
不直观 要自己计算各种时间差

PerformanceObserver 的原理

PerformanceObserver = 性能观察器。

让你实时监控浏览器的各种性能数据,浏览器会主动通知你。

yaml 复制代码
PerformanceObserver 能监控的数据:
┌───────────────────────────────────┐
│  longtask      - 长任务(>50ms)    │
│  paint         - 绘制时间(FP/FCP) │
│  resource      - 资源加载时间       │
│  navigation    - 页面导航时间       │
│  mark          - 自定义标记         │
│  measure       - 自定义测量         │
└───────────────────────────────────┘

基本用法

javascript 复制代码
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.log('性能数据:', entry);
  });
});

// 观察长任务
observer.observe({ type: 'longtask', buffered: true });

// 观察绘制时间
observer.observe({ type: 'paint', buffered: true });

// 观察资源加载
observer.observe({ type: 'resource', buffered: true });

实际应用:检测长任务

javascript 复制代码
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach(entry => {
    console.warn('检测到长任务:', entry.duration, 'ms');

    // 长任务超过 50ms,视为需要优化
    if (entry.duration > 50) {
      console.error('长任务位置:', entry.name);
      console.error('开始时间:', entry.startTime);
    }
  });
});

observer.observe({ type: 'longtask', buffered: true });

Page Visibility API --- 页面可见性检测

故事的起因:用户切换标签页你不知道

你有没有想过:

  • 用户打开了你的页面,然后切到别的标签页
  • 你的动画还在跑吗?定时器还在跑吗?
  • 如果是视频网站,用户切走了,视频还在播放吗?

以前的解决方案:

javascript 复制代码
window.addEventListener('blur', () => {
  // 窗口失去焦点
});

window.addEventListener('focus', () => {
  // 窗口获得焦点
});

但这不够精确------用户可能只是最小化了窗口 ,或者切换到了别的标签页

Page Visibility API 的原理

Page Visibility API 让你精确知道页面的可见状态

yaml 复制代码
visibilityState 的两种状态:
┌─────────────────────────────────────┐
│  visible      - 页面完全可见         │
│  hidden       - 页面被隐藏          │
└─────────────────────────────────────┘

触发场景:
- 切换标签页 → hidden
- 最小化窗口 → hidden
- 关闭浏览器 → hidden
- 切换应用 → hidden

基本用法

javascript 复制代码
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    console.log('页面被隐藏了');
    pauseAnimations();
    stopPolling();
  } else {
    console.log('页面可见了');
    resumeAnimations();
    startPolling();
  }
});

console.log('当前状态:', document.visibilityState);

实际应用:标签页切换统计

javascript 复制代码
const metrics = {
  visibleTime: 0,
  hiddenTime: 0,
  lastChangeTime: Date.now()
};

document.addEventListener('visibilitychange', () => {
  const now = Date.now();

  if (document.hidden) {
    // 开始隐藏,记录可见时长
    metrics.visibleTime += now - metrics.lastChangeTime;
    metrics.lastChangeTime = now;

    // 暂停音乐/视频
    video.pause();
  } else {
    // 结束隐藏,记录隐藏时长
    metrics.hiddenTime += now - metrics.lastChangeTime;
    metrics.lastChangeTime = now;

    // 恢复音乐/视频
    video.play();
  }
});

window.addEventListener('beforeunload', () => {
  navigator.sendBeacon('/analytics', JSON.stringify(metrics));
});

Battery API --- 电池状态检测

故事的起因:低电量时该省电

做 Web 应用时,你可能想过:

  • 电量低的时候,要不要关闭动画省电?
  • 正在充电时,能不能开启高性能模式?
  • 用户还能用多久?

这些问题,Battery API 都能回答。

Battery API 的兼容性

浏览器 支持情况
Chrome ✅ 50+
Firefox ✅ 10+
Safari ❌ 不支持
Edge ✅ 79+

注意:Safari 和 iOS Safari 不支持 Battery API。如果你的用户主要是苹果设备,这个 API 就用不了。

Battery API 的原理

Battery API = 电池状态接口。

浏览器通过操作系统获取电池信息,暴露给 JavaScript。

yaml 复制代码
Battery API 数据来源:
┌─────────────────────────────────────┐
│         浏览器                        │
│  navigator.getBattery()              │
└─────────────────────────────────────┘
          ↓
┌─────────────────────────────────────┐
│         操作系统                      │
│  Windows / macOS / Linux            │
│  提供电池状态、电量、充电时间          │
└─────────────────────────────────────┘
          ↓
┌─────────────────────────────────────┐
│         硬件                          │
│  电池芯片提供实时数据                 │
└─────────────────────────────────────┘

基本用法

javascript 复制代码
navigator.getBattery().then(battery => {
  console.log('是否在充电:', battery.charging);
  console.log('电量:', (battery.level * 100).toFixed(0), '%');
  console.log('剩余时间:', battery.dischargingTime / 60, '分钟');

  // 监听电量变化
  battery.addEventListener('levelchange', () => {
    console.log('电量变化:', (battery.level * 100).toFixed(0), '%');
  });

  // 监听充电状态变化
  battery.addEventListener('chargingchange', () => {
    console.log('充电状态变化:', battery.charging ? '充电中' : '未充电');
  });
});

实际应用:省电模式

javascript 复制代码
// 获取电池信息并初始化省电模式
navigator.getBattery().then(battery => {
  // 根据电池状态更新页面
  function updateMode() {
    // 判断是否需要省电:没在充电 且 电量低于 20%
    const shouldSavePower = !battery.charging && battery.level < 0.2;

    // 切换 body 的省电样式类
    document.body.classList.toggle('power-saving', shouldSavePower);

    if (shouldSavePower) {
      // 电量低:关闭动画、停止轮询、降低画质
      disableAnimations();
      stopAutoRefresh();
      reduceRenderQuality();
    } else {
      // 电量充足:恢复正常模式
      enableAnimations();
      startAutoRefresh();
      restoreRenderQuality();
    }
  }

  // 监听电量变化和充电状态变化
  battery.addEventListener('levelchange', updateMode);
  battery.addEventListener('chargingchange', updateMode);

  // 页面加载时先检查一次
  updateMode();
});

Clipboard API --- 剪贴板读写

故事的起因:以前只能靠 document.execCommand

以前复制文本到剪贴板,是这样的:

javascript 复制代码
// ❌ 这种方式已经废弃了
textarea.select();
document.execCommand('copy');

而且这种方式问题很多:

  • 只能复制文本
  • 体验差(会有选中效果)
  • API 废弃了

Clipboard API 的原理

Clipboard API 让你安全地读写剪贴板,支持文本、图片、任意数据。

方法 作用
navigator.clipboard.readText() 读取剪贴板文本
navigator.clipboard.writeText() 写入文本到剪贴板
navigator.clipboard.read() 读取任意数据(图片等)
navigator.clipboard.write() 写入任意数据

注意:读写剪贴板需要用户授权!

基本用法

javascript 复制代码
// 复制文本
async function copyText(text) {
  try {
    await navigator.clipboard.writeText(text);
    showToast('复制成功!');
  } catch (err) {
    console.error('复制失败:', err);
  }
}

// 读取剪贴板
async function readClipboard() {
  try {
    const text = await navigator.clipboard.readText();
    console.log('剪贴板内容:', text);
    return text;
  } catch (err) {
    console.error('读取失败:', err);
  }
}

实际应用:一键复制代码

javascript 复制代码
document.querySelectorAll('.code-block').forEach(block => {
  const button = document.createElement('button');
  button.className = 'copy-btn';
  button.textContent = '复制';

  button.addEventListener('click', async () => {
    const code = block.textContent;

    try {
      await navigator.clipboard.writeText(code);
      button.textContent = '已复制!';
      setTimeout(() => {
        button.textContent = '复制';
      }, 2000);
    } catch (err) {
      button.textContent = '复制失败';
    }
  });

  block.appendChild(button);
});

Geolocation API --- 地理位置

故事的起因:LBS 应用越来越火

地图、打车、外卖、社交软件......越来越多的应用需要知道用户的位置

Geolocation API 就是浏览器提供的定位接口

Geolocation API 的原理

Geolocation API = 地理位置接口。

浏览器会调用系统定位服务来获取位置。

yaml 复制代码
Geolocation 定位方式:
┌───────────────────────────────────┐
│  GPS 定位      - 精度最高(1-10米)  │
│  WLAN 定位     - 通过 WiFi 路由器    │
│  基站定位      - 通过手机信号塔       │
│  IP 定位       - 精度最低(城市级)   │
└───────────────────────────────────┘
浏览器会自动选择最优方式

为什么需要用户授权?

原因 说明
隐私 位置信息属于敏感个人信息
法律要求 GDPR 等法规要求明确授权
安全 防止网站偷偷获取位置

基本用法

javascript 复制代码
navigator.geolocation.getCurrentPosition(
  (position) => {
    console.log('纬度:', position.coords.latitude);
    console.log('经度:', position.coords.longitude);
    console.log('精度:', position.coords.accuracy, '米');
  },
  (error) => {
    console.error('获取失败:', error.message);
  },
  {
    enableHighAccuracy: true,  // 高精度模式(更慢)
    timeout: 5000,            // 超时时间
    maximumAge: 0            // 不使用缓存
  }
);

监听位置变化

javascript 复制代码
const watchId = navigator.geolocation.watchPosition(
  (position) => {
    updateMapPosition(position.coords.latitude, position.coords.longitude);
  },
  (error) => {
    console.error('监听失败:', error.message);
  },
  {
    enableHighAccuracy: true,
    timeout: 10000,
    maximumAge: 30000  // 缓存 30 秒
  }
);

// 停止监听
navigator.geolocation.clearWatch(watchId);

实际应用:距离计算

javascript 复制代码
function calculateDistance(lat1, lon1, lat2, lon2) {
  const R = 6371;  // 地球半径(公里)

  const toRad = (deg) => deg * Math.PI / 180;

  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);

  const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
            Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
            Math.sin(dLon/2) * Math.sin(dLon/2);

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

  return R * c;
}

// 北京 (39.9042, 116.4074) 到 上海 (31.2304, 121.4737)
const distance = calculateDistance(39.9042, 116.4074, 31.2304, 121.4737);
console.log('北京到上海约:', distance.toFixed(0), '公里');

总结

神器一览表

API 作用 解决的问题 兼容性
IntersectionObserver 懒加载、无限滚动 scroll 事件性能差 ✅ 主流都支持
MutationObserver DOM 变化监控 MutationEvent 已废弃 ✅ 主流都支持
ResizeObserver 元素大小监听 window.resize 太粗糙 ✅ 主流都支持
PerformanceObserver 性能监控 性能数据不直观 ✅ 主流都支持
Page Visibility API 页面可见性 不知道用户是否在看 ✅ 主流都支持
Battery API 电池状态 无法感知电量 ⚠️ Safari 不支持
Clipboard API 剪贴板读写 execCommand 废弃 ✅ 主流都支持
Geolocation API 地理位置 需要 LBS 功能 ✅ 主流都支持

使用建议

yaml 复制代码
✅ 推荐大胆使用的:
- IntersectionObserver:懒加载必备
- Page Visibility API:标签页切换必备
- Clipboard API:复制粘贴体验升级
- ResizeObserver:响应式布局神器

⚠️ 需要兼容性处理的:
- Battery API:先检测,不支持就降级
- Geolocation API:用户授权,注意隐私

❌ 已废弃不要用的:
- MutationEvent
- document.execCommand(复制除外)

写在最后

现在你知道了:

  • 这些 API 不是浏览器随便加的,而是解决了真实问题
  • IntersectionObserver 让懒加载变得简单高效
  • MutationObserver 是 DOM 变化监控的唯一选择
  • ResizeObserver 解决了 window.resize 的痛点
  • PerformanceObserver 让性能监控变得直观
  • Page Visibility API 让你知道用户在看什么
  • Battery API 可以做省电优化(Safari 除外)
  • Clipboard API 是现代复制粘贴的标准方案
  • Geolocation API 让网页也能做 LBS 应用

每个 API 的存在都有其意义------用对了,问题迎刃而解

下次遇到相关场景,记得试试这些神器!

相关推荐
WebInfra2 小时前
Rspack 2.0 正式发布!
前端·javascript·前端框架
极速蜗牛2 小时前
Cursor最近变傻了?
前端
码字小学妹2 小时前
Claude Opus 4.7 接入指南(2026):国内配置 + xhigh 推理 + 成本计算
前端
小赵同学WoW2 小时前
插槽【vue2】与 【vue3】对比
前端
代码随想录2 小时前
Agent大厂面试题汇总:ReAct、Function Calling、MCP、RAG高频问题
前端·react.js·前端框架
前端那点事2 小时前
Vue响应式原理|从底层实现到面试考点,一文吃透(Vue2+Vue3全解析)
前端·vue.js
walking9572 小时前
Vite 打包优化终极指南:从 30MB 到 800KB 的性能飞跃
前端·vue.js·vite
Highcharts.js2 小时前
在 React 中使用 useState 和 @highcharts/react 构建动态图表
开发语言·前端·javascript·react.js·信息可视化·前端框架·highcharts
梓言2 小时前
解决 Element Plus 中 Tooltip 样式影响全局菜单(Menu)及宽度控制失效的完美方案
前端·css·element