Navigator.sendBeacon API 教程
简介
navigator.sendBeacon()
是一个 Web API,它提供了一种可靠的方式来发送小量数据到服务器,特别适用于在页面卸载(unload)时发送分析或诊断数据。与传统的 XMLHttpRequest 或 fetch 不同,sendBeacon 请求不会因为页面导航或关闭而被取消。
为什么需要 sendBeacon?
在 sendBeacon 出现之前,开发者通常使用以下方法在页面卸载时发送数据:
- 在
unload
或beforeunload
事件中使用同步 XMLHttpRequest - 设置
navigator.sendBeacon = false
以延迟页面卸载 - 使用
<img>
标签的 src 属性发送信标请求
这些方法都有不同的缺点:同步请求会阻塞页面卸载,影响用户体验;延迟卸载也会导致页面响应变慢;而图片信标方法可能不可靠。
sendBeacon
API 解决了这些问题,它保证了:
- 请求以异步方式处理,不会阻塞页面导航
- 浏览器会尽最大努力发送这些请求,即使页面已关闭
- 简单易用,无需复杂的配置
基本语法
const success = navigator.sendBeacon(url, data);
参数:
-
url
:接收数据的服务器端点 -
data
(可选):要发送的数据,可以是以下类型:
ArrayBuffer
ArrayBufferView
Blob
DOMString
FormData
URLSearchParams
返回值:
- 布尔值,表示浏览器是否已将数据加入到发送队列中
使用示例
基本用法
window.addEventListener('unload', () => {
navigator.sendBeacon('/analytics', 'page-visit-ended');
});
发送 JSON 数据
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
const analyticsData = {
event: 'page_hide',
timeSpent: calculateTimeSpent(),
scrollDepth: getScrollDepth()
};
const blob = new Blob([JSON.stringify(analyticsData)], {type: 'application/json'});
navigator.sendBeacon('/analytics/events', blob);
}
});
使用 FormData
window.addEventListener('beforeunload', () => {
const formData = new FormData();
formData.append('event', 'page_unload');
formData.append('userId', getUserId());
formData.append('sessionDuration', getSessionDuration());
navigator.sendBeacon('/analytics/collect', formData);
});
最佳实践
-
数据量控制:sendBeacon 适合发送小量数据,大多数浏览器限制为 64KB,超过这个大小可能会失败。
-
选择正确的事件:
visibilitychange
:页面变为不可见时触发,比 unload 更可靠pagehide
:页面隐藏或卸载时触发beforeunload
:页面即将卸载时触发unload
:页面卸载时触发(不推荐,但仍然广泛使用)
-
检查浏览器支持:
if (navigator.sendBeacon) { // 使用 sendBeacon } else { // 回退到其他方法 }
-
检查发送状态:
const success = navigator.sendBeacon('/endpoint', data); if (!success) { // 发送失败,考虑回退方法 }
-
服务器端处理:服务器应配置为正确处理 POST 请求,Content-Type 通常为:
text/plain
(字符串数据)application/json
(JSON 数据)multipart/form-data
(FormData 对象)
浏览器兼容性
sendBeacon API 在现代浏览器中得到广泛支持:
- Chrome 39+
- Firefox 31+
- Safari 11.1+
- Edge 14+
- Opera 26+
移动浏览器:
- Android Chrome 39+
- iOS Safari 11.3+
实际应用场景
- 分析追踪:记录用户行为和页面性能数据
- 会话日志:记录用户会话的结束时间和持续时间
- 错误报告:在页面崩溃或关闭前发送错误日志
- 用户活动跟踪:记录页面交互数据
- 广告展示统计:发送广告展示和交互数据
与其他技术的比较
特性 | sendBeacon | XMLHttpRequest | Fetch | 图片信标 |
---|---|---|---|---|
页面卸载时可靠性 | 高 | 低(除非同步) | 低 | 中 |
阻塞页面导航 | 否 | 可能(如果同步) | 否 | 否 |
支持多种数据格式 | 是 | 是 | 是 | 否(仅 URL 参数) |
可控制请求头 | 否 | 是 | 是 | 否 |
可接收响应 | 否 | 是 | 是 | 有限 |
限制和注意事项
- 没有响应处理:sendBeacon 是单向通信,无法处理服务器响应
- 无法设置请求头:自定义请求头不被支持
- 数据大小限制:通常限制为 64KB
- 没有重试机制:如果发送失败,不会自动重试
- CORS 限制:仍然受到同源策略的限制,跨域请求需要服务器支持
完整示例:用户行为跟踪
class AnalyticsTracker {
constructor() {
this.data = {
startTime: Date.now(),
pageViews: 1,
clicks: 0,
scrollDepth: 0,
url: window.location.href
};
this.setupEventListeners();
}
setupEventListeners() {
// 记录点击
document.addEventListener('click', () => {
this.data.clicks++;
});
// 记录滚动深度
window.addEventListener('scroll', () => {
const scrollTop = window.scrollY;
const docHeight = document.body.offsetHeight;
const winHeight = window.innerHeight;
const scrollPercent = scrollTop / (docHeight - winHeight);
this.data.scrollDepth = Math.max(
this.data.scrollDepth,
Math.round(scrollPercent * 100)
);
});
// 页面隐藏时发送数据
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
this.sendData();
}
});
// 页面卸载时发送数据
window.addEventListener('beforeunload', () => {
this.sendData();
});
}
sendData() {
// 计算停留时间
this.data.timeSpent = Date.now() - this.data.startTime;
// 准备发送的数据
const blob = new Blob(
[JSON.stringify(this.data)],
{type: 'application/json'}
);
// 使用 sendBeacon 发送数据
if (navigator.sendBeacon('/analytics/collect', blob)) {
console.log('Analytics data queued for sending');
} else {
console.error('Failed to queue analytics data');
// 可以实现备选方案
}
}
}
// 初始化跟踪器
const tracker = new AnalyticsTracker();
服务器端示例(Node.js)
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
// 支持 JSON 请求体
app.use(bodyParser.json());
// 支持 URL 编码的请求体
app.use(bodyParser.urlencoded({ extended: true }));
// 支持原始请求体
app.use(bodyParser.raw({ type: '*/*' }));
// 处理 beacon 请求的端点
app.post('/analytics/collect', (req, res) => {
let data;
// 根据 Content-Type 解析数据
const contentType = req.headers['content-type'] || '';
if (contentType.includes('application/json')) {
data = req.body; // 已由 bodyParser.json() 解析
} else if (contentType.includes('text/plain')) {
data = req.body.toString();
} else if (contentType.includes('application/x-www-form-urlencoded')) {
data = req.body; // 已由 bodyParser.urlencoded() 解析
} else {
// 处理其他格式
data = req.body;
}
// 记录或处理数据
console.log('Received beacon data:', data);
// sendBeacon 不关心响应,但我们仍然发送一个
res.status(202).send('Accepted');
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
总结
Navigator.sendBeacon API 为网站提供了一种可靠的方式来发送用户离开页面时的数据,而不会影响用户体验。它特别适合分析、日志记录和诊断数据的收集。虽然它有一些限制,如无法处理响应和自定义请求头,但对于它的设计目的来说,这是一个简单而有效的解决方案。
随着用户隐私意识的提高和浏览器策略的变化,开发者应该负责任地使用这个 API,确保透明地收集数据并遵守相关的隐私法规。