Navigator.sendBeacon() 方法详解
navigator.sendBeacon()
是现代浏览器提供的一个API,专门用于在页面卸载(unload)阶段向服务器可靠地发送少量数据。
基本概念
用途
sendBeacon
主要用于解决传统方法(如XMLHttpRequest或fetch)在页面卸载时无法可靠发送数据的问题。常见使用场景包括:
- 发送分析数据(如页面访问统计)
- 记录用户行为(如最后停留时间)
- 上报错误日志
- 保存用户表单输入(防止意外关闭时丢失)
语法
navigator.sendBeacon(url, data);
url
: 接收数据的服务器URLdata
: 要发送的数据,可以是ArrayBuffer、ArrayBufferView、Blob、DOMString、FormData或URLSearchParams
返回值
返回布尔值:
true
: 浏览器已成功将数据加入发送队列false
: 发送失败(如URL无效、数据过大等)
主要特点
-
异步且不阻塞页面卸载
- 不会延迟页面卸载或导航
- 浏览器会在后台处理请求
-
高优先级
- 相比普通AJAX请求有更高的发送优先级
-
跨域支持
- 遵循CORS规则
- 可以携带cookie(需设置credentials)
-
可靠传输
- 即使在页面关闭后,浏览器也会尽量确保请求完成
-
数据大小限制
- 不同浏览器实现不同,通常限制在64KB以内
与传统方法的对比
特性 | sendBeacon | XMLHttpRequest | fetch | Image Beacon |
---|---|---|---|---|
卸载时可用 | ✔️ | ❌ | ❌ | ✔️ |
不阻塞卸载 | ✔️ | ❌ | ❌ | ✔️ |
支持POST | ✔️ | ✔️ | ✔️ | ❌ (GET only) |
支持多种数据类型 | ✔️ | ✔️ | ✔️ | ❌ |
异步处理 | ✔️ | ✔️ | ✔️ | ✔️ |
CORS支持 | ✔️ | ✔️ | ✔️ | ✔️ |
使用示例
基本用法
// 在页面卸载时发送数据
window.addEventListener('unload', function() {
const data = JSON.stringify({
page: window.location.href,
time: Date.now()
});
navigator.sendBeacon('/analytics', data);
});
发送FormData
const data = new FormData();
data.append('event', 'pageview');
data.append('timestamp', Date.now());
navigator.sendBeacon('/track', data);
带有类型信息的Blob数据
const blob = new Blob([JSON.stringify({ key: 'value' })], {
type: 'application/json'
});
navigator.sendBeacon('/log', blob);
最佳实践
-
数据量控制
- 保持数据尽可能小,以提高可靠性
-
错误处理
if (!navigator.sendBeacon(url, data)) { // 备用方案 fetch(url, { method: 'POST', body: data, keepalive: true }); }
-
Content-Type设置
- 对于非FormData/URLSearchParams数据,服务器可能需要特殊处理
- 可以通过Blob指定类型
-
与visibilitychange事件结合
document.addEventListener('visibilitychange', function() { if (document.visibilityState === 'hidden') { navigator.sendBeacon('/visibility-change', data); } });
浏览器兼容性
- 现代浏览器普遍支持(Chrome 39+, Firefox 31+, Edge 14+, Safari 11.1+)
- 不支持IE
- 移动端浏览器支持良好
注意事项
-
无法自定义请求头
- 只能通过数据本身或URL传递信息
-
无法获取响应
- 单向发送,不关心服务器响应
-
可能被ad-blockers拦截
- 某些广告拦截器会拦截beacon请求
-
性能影响
- 虽然设计为低影响,但大量使用仍可能影响性能
替代方案
如果sendBeacon
不可用,可以考虑:
fetch
withkeepalive
标志fetch(url, { method: 'POST', body: data, keepalive: true });
- 传统的Image Beacon(仅限GET请求)
new Image().src = '/track.gif?' + queryString;
服务器端处理
服务器接收到的请求与普通HTTP请求类似,但要注意:
- 可能需要处理不同的Content-Type
- 请求可能在页面关闭后到达
- 可能没有Referer头信息
navigator.sendBeacon()
是为解决特定场景下的数据上报问题而设计的专用API,在分析统计、日志记录等场景下能提供更可靠的解决方案。
使用Navigator.sendBeacon()进行数据埋点的完整指南
数据埋点是网站分析的基础,navigator.sendBeacon()
因其在页面卸载时的可靠性成为埋点的理想选择。下面详细介绍如何使用这一技术进行全方位数据埋点。
一、基础埋点实现
1. 页面访问跟踪
// 页面展现埋点
document.addEventListener('DOMContentLoaded', function() {
const pageViewData = {
event: 'pageview',
url: window.location.href,
referrer: document.referrer,
timestamp: Date.now(),
title: document.title,
screen: `${window.screen.width}x${window.screen.height}`,
language: navigator.language
};
navigator.sendBeacon('/track', JSON.stringify(pageViewData));
});
2. 用户行为跟踪
// 按钮点击跟踪
document.querySelectorAll('[data-track]').forEach(element => {
element.addEventListener('click', function() {
const clickData = {
event: 'click',
element: this.getAttribute('data-track'),
text: this.textContent.trim(),
href: this.href || '',
position: getElementPosition(this), // 自定义获取元素位置函数
timestamp: Date.now()
};
navigator.sendBeacon('/track', JSON.stringify(clickData));
});
});
// 滚动深度跟踪
let lastReportedScroll = 0;
window.addEventListener('scroll', _.throttle(() => {
const scrollDepth = getScrollDepth(); // 获取滚动百分比
if (scrollDepth - lastReportedScroll > 10) { // 每增加10%报告一次
const scrollData = {
event: 'scroll',
depth: scrollDepth,
timestamp: Date.now()
};
navigator.sendBeacon('/track', JSON.stringify(scrollData));
lastReportedScroll = scrollDepth;
}
}, 1000));
二、高级埋点策略
1. 会话跟踪
// 生成唯一会话ID
const sessionId = 'session_' + Math.random().toString(36).substr(2, 9);
sessionStorage.setItem('sessionId', sessionId);
// 页面卸载时发送会话结束信号
window.addEventListener('beforeunload', function() {
const sessionData = {
event: 'session_end',
sessionId: sessionId,
duration: getSessionDuration(), // 计算会话时长
pageCount: getPageViewCount(), // 获取本次会话页面浏览数
timestamp: Date.now()
};
navigator.sendBeacon('/track', JSON.stringify(sessionData));
});
2. 性能监控
// 使用Performance API获取性能数据
window.addEventListener('load', function() {
const timing = performance.timing;
const perfData = {
event: 'performance',
dns: timing.domainLookupEnd - timing.domainLookupStart,
tcp: timing.connectEnd - timing.connectStart,
ttfb: timing.responseStart - timing.requestStart,
domReady: timing.domComplete - timing.domLoading,
loadTime: timing.loadEventEnd - timing.navigationStart,
memory: performance.memory ? performance.memory.usedJSHeapSize : null
};
navigator.sendBeacon('/track', JSON.stringify(perfData));
});
3. 错误追踪
// 全局错误捕获
window.onerror = function(message, source, lineno, colno, error) {
const errorData = {
event: 'error',
message: message,
source: source,
line: lineno,
column: colno,
stack: error && error.stack,
timestamp: Date.now()
};
navigator.sendBeacon('/track', JSON.stringify(errorData));
};
// Promise未捕获异常
window.addEventListener('unhandledrejection', function(event) {
const promiseData = {
event: 'unhandled_rejection',
reason: event.reason,
timestamp: Date.now()
};
navigator.sendBeacon('/track', JSON.stringify(promiseData));
});
三、优化技巧
1. 数据压缩
function compressData(data) {
// 简单示例:移除空格缩短JSON
return JSON.stringify(data).replace(/\s+/g, '');
// 生产环境可考虑更复杂的压缩算法
}
2. 批量发送
const batchQueue = [];
const BATCH_SIZE = 5;
const BATCH_TIMEOUT = 5000;
function trackEvent(eventData) {
batchQueue.push(eventData);
if (batchQueue.length >= BATCH_SIZE) {
sendBatch();
} else if (!batchTimer) {
batchTimer = setTimeout(sendBatch, BATCH_TIMEOUT);
}
}
function sendBatch() {
if (batchQueue.length > 0) {
navigator.sendBeacon('/track', JSON.stringify({
event: 'batch',
items: batchQueue
}));
batchQueue.length = 0;
}
clearTimeout(batchTimer);
batchTimer = null;
}
// 页面卸载时确保发送剩余数据
window.addEventListener('beforeunload', sendBatch);
3. 用户识别
// 生成持久用户ID
function getUserId() {
let userId = localStorage.getItem('userId');
if (!userId) {
userId = 'user_' + Math.random().toString(36).substr(2, 9);
localStorage.setItem('userId', userId);
}
return userId;
}
// 在埋点数据中包含用户ID
const baseData = {
userId: getUserId(),
sessionId: sessionStorage.getItem('sessionId'),
// 其他通用字段...
};
四、服务器端处理建议
1. Node.js示例
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
app.post('/track', bodyParser.text(), (req, res) => {
try {
const data = JSON.parse(req.body);
// 验证必要字段
if (!data.event || !data.timestamp) {
return res.status(400).send('Invalid data');
}
// 添加服务器时间
data.serverTime = new Date().toISOString();
// 存储到数据库或分析系统
saveToDatabase(data);
res.status(200).send('OK');
} catch (err) {
console.error('Tracking error:', err);
res.status(500).send('Error');
}
});
function saveToDatabase(data) {
// 实现数据库存储逻辑
console.log('Tracking data:', data);
}
2. 数据存储建议
- 数据结构化: 使用专门的分析数据库(如ClickHouse)
- 数据分区: 按日期/事件类型分区提高查询效率
- 数据清洗: 存储前验证和清理数据
五、调试与验证
1. 开发环境调试
// 重写sendBeacon进行调试
if (process.env.NODE_ENV === 'development') {
navigator.sendBeacon = function(url, data) {
console.log('Beacon Data:', JSON.parse(data));
return true;
};
}
2. 网络请求监控
使用Chrome开发者工具的Network面板:
- 过滤"beacon"类型请求
- 检查请求负载和响应
- 验证CORS头部是否正确设置
3. 数据质量检查
- 完整性检查: 确保所有关键事件都被捕获
- 一致性检查: 验证跨页面用户/会话ID是否一致
- 时间序列分析: 检查时间戳是否合理
六、隐私与合规考虑
-
GDPR/CCPA合规
// 检查用户同意状态 function canTrack() { return window.consent && window.consent.analytics; } // 在所有埋点调用前检查 if (canTrack()) { navigator.sendBeacon('/track', data); }
-
数据匿名化
// 移除或哈希处理PII(个人身份信息) function anonymize(data) { if (data.email) { data.emailHash = sha256(data.email); delete data.email; } return data; }
-
DNT(Do Not Track)尊重
if (navigator.doNotTrack !== '1') { // 执行跟踪代码 }
通过以上方法,您可以构建一个全面、可靠且高效的数据埋点系统。navigator.sendBeacon()
的特别优势在于能够可靠捕获用户离开页面前的最后行为,这是传统AJAX请求难以实现的。结合其他跟踪技术,可以获取完整的用户行为画像。