你以为浏览器只能打开网页?打开控制台敲
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-class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="real-image.jpg" class="lazy" alt="加载中..."> <img data-class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="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 的存在都有其意义——用对了,问题迎刃而解。
下次遇到相关场景,记得试试这些神器!
共同学习,写下你的评论
评论加载中...
作者其他优质文章