你以为浏览器只能打开网页?打开控制台敲
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体验AI代码助手代码解读复制代码window.addEventListener('scroll', () => { const rect = element.getBoundingClientRect(); if (rect.top < window.innerHeight) { loadImage(); } });这段代码有什么问题?
| 问题 | 说明 |
|---|---|
| 性能差 | scroll 事件每秒触发几十次,每次都计算 rect |
| 无法批量 | 多个元素要写多个监听 |
| 滚动卡顿 | 计算太频繁,导致页面卡 |
就像门口站了个保安:每进来一个人,他都要站起来看一眼是不是 VIP——累死了。
IntersectionObserver 的原理
IntersectionObserver = 交叉观察器。
浏览器提供了这个 API,让你能高效地检测元素是否进入视口。
原理很简单:
yaml体验AI代码助手代码解读复制代码IntersectionObserver 工作原理: ┌────────────────────────────┐ │ 视口(Viewport) │ │ ┌────────────────────┐ │ │ │ target 元素 │ │ │ │ │ │ │ └────────────────────┘ │ └────────────────────────────┘ ↓ 元素进入视口?浏览器自动通知你
基本用法
javascript体验AI代码助手代码解读复制代码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=完全可见 |
实际应用:懒加载图片
这是最经典的应用场景: 0 1 2 3 4 5 6 7 8 9
html体验AI代码助手代码解读复制代码<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体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码$('#container').bind('DOMNodeInserted', function() { console.log('有新内容了'); });后来有了 MutationEvent:
javascript体验AI代码助手代码解读复制代码document.addEventListener('DOMNodeInserted', (e) => { console.log('有新节点:', e.target); });但这些事件有严重问题:
| 问题 | 说明 |
|---|---|
| 性能灾难 | 每次 DOM 变化都触发,变化多了直接卡死 |
| 不准确 | 不能区分是内容变了还是属性变了 |
| 已废弃 | 现代浏览器不再推荐使用 |
MutationObserver 的原理
MutationObserver = 变化观察器。
浏览器用它来高效地监听 DOM 变化,变化会被批量收集,一次性通知你。
yaml体验AI代码助手代码解读复制代码MutationObserver 原理: ┌──────────────────────────────┐ │ DOM 树 │ │ <div id="app"> │ │ <p>你好</p> ← 变化 │ │ </div> │ └──────────────────────────────┘ ↓ 变化被收集 → 批量通知 → 一次回调
基本用法
javascript体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码变化收集流程: 1. DOM 发生变化 2. 变化被记录到队列(不立即通知) 3. 变化积累一定数量或时间后 4. 批量通知观察者
实际应用:表单变化检测
监听表单是否有未保存的修改:
javascript体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码window.addEventListener('resize', () => { console.log('窗口大小:', window.innerWidth, window.innerHeight); });但这有两个问题:
| 问题 | 说明 |
|---|---|
| 不精确 | 只能监听窗口,不能监听某个 div |
| 性能差 | 窗口每次 resize 都触发,频率很高 |
ResizeObserver 的原理
ResizeObserver = 大小观察器。
专门用来监听任意元素的大小变化,精准高效。
yaml体验AI代码助手代码解读复制代码ResizeObserver 原理: ┌─────────────────────────┐ │ <div class="box"> │ │ 内容随着大小变化 │ │ </div> │ └─────────────────────────┘ ↓ 大小变化 → 浏览器自动通知 ↓ entry.contentRect 包含新尺寸
基本用法
javascript体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码// 获取页面加载时间 const loadTime = performance.timing.loadEventEnd - performance.timing.navigationStart; console.log('页面加载时间:', loadTime);但问题是:
| 问题 | 说明 |
|---|---|
| 一次性 | 数据只在特定时间点有效 |
| 不实时 | 无法监控动态加载的资源 |
| 不直观 | 要自己计算各种时间差 |
PerformanceObserver 的原理
PerformanceObserver = 性能观察器。
让你实时监控浏览器的各种性能数据,浏览器会主动通知你。
yaml体验AI代码助手代码解读复制代码PerformanceObserver 能监控的数据: ┌───────────────────────────────────┐ │ longtask - 长任务(>50ms) │ │ paint - 绘制时间(FP/FCP) │ │ resource - 资源加载时间 │ │ navigation - 页面导航时间 │ │ mark - 自定义标记 │ │ measure - 自定义测量 │ └───────────────────────────────────┘
基本用法
javascript体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码window.addEventListener('blur', () => { // 窗口失去焦点 }); window.addEventListener('focus', () => { // 窗口获得焦点 });但这不够精确——用户可能只是最小化了窗口,或者切换到了别的标签页。
Page Visibility API 的原理
Page Visibility API 让你精确知道页面的可见状态。
yaml体验AI代码助手代码解读复制代码visibilityState 的两种状态: ┌─────────────────────────────────────┐ │ visible - 页面完全可见 │ │ hidden - 页面被隐藏 │ └─────────────────────────────────────┘ 触发场景: - 切换标签页 → hidden - 最小化窗口 → hidden - 关闭浏览器 → hidden - 切换应用 → hidden
基本用法
javascript体验AI代码助手代码解读复制代码document.addEventListener('visibilitychange', () => { if (document.hidden) { console.log('页面被隐藏了'); pauseAnimations(); stopPolling(); } else { console.log('页面可见了'); resumeAnimations(); startPolling(); } }); console.log('当前状态:', document.visibilityState);实际应用:标签页切换统计
javascript体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码Battery API 数据来源: ┌─────────────────────────────────────┐ │ 浏览器 │ │ navigator.getBattery() │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 操作系统 │ │ Windows / macOS / Linux │ │ 提供电池状态、电量、充电时间 │ └─────────────────────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ 硬件 │ │ 电池芯片提供实时数据 │ └─────────────────────────────────────┘
基本用法
javascript体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码// 获取电池信息并初始化省电模式 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体验AI代码助手代码解读复制代码// ❌ 这种方式已经废弃了 textarea.select(); document.execCommand('copy');而且这种方式问题很多:
只能复制文本
体验差(会有选中效果)
API 废弃了
Clipboard API 的原理
Clipboard API 让你安全地读写剪贴板,支持文本、图片、任意数据。
| 方法 | 作用 |
|---|---|
navigator.clipboard.readText() | 读取剪贴板文本 |
navigator.clipboard.writeText() | 写入文本到剪贴板 |
navigator.clipboard.read() | 读取任意数据(图片等) |
navigator.clipboard.write() | 写入任意数据 |
注意:读写剪贴板需要用户授权!
基本用法
javascript体验AI代码助手代码解读复制代码// 复制文本 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体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码Geolocation 定位方式: ┌───────────────────────────────────┐ │ GPS 定位 - 精度最高(1-10米) │ │ WLAN 定位 - 通过 WiFi 路由器 │ │ 基站定位 - 通过手机信号塔 │ │ IP 定位 - 精度最低(城市级) │ └───────────────────────────────────┘ 浏览器会自动选择最优方式
为什么需要用户授权?
| 原因 | 说明 |
|---|---|
| 隐私 | 位置信息属于敏感个人信息 |
| 法律要求 | GDPR 等法规要求明确授权 |
| 安全 | 防止网站偷偷获取位置 |
基本用法
javascript体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码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体验AI代码助手代码解读复制代码✅ 推荐大胆使用的: - 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 的存在都有其意义——用对了,问题迎刃而解。
下次遇到相关场景,记得试试这些神器!