你以为浏览器只能打开网页?打开控制台敲
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 的存在都有其意义------用对了,问题迎刃而解。
下次遇到相关场景,记得试试这些神器!