一、前言
最近老板需要在站点里获取一些用户操作数据来进行数据分析,从来没有接触过前端埋点的我也只能在网上学习学习来实现。本文会根据特定场景提供一个实现前端埋点sdk实现的思路,希望能够对观者提供一丁点帮助。
二、sdk设计
- 采用面向对象编程的类;
- 能够传递配置参数(上报数据地址、版本等);
- 决定将sdk的执行分为三个阶段:
- sdk加载时(上报页面信息,添加监听器);
- 页面加载完成时(计算加载完成时间,开始计算用户停留时间等);
- 页面卸载时(上报监听器记录的用户操作等数据);
- 考虑到错误捕获;
三、实现
sdk代码大致实现:
// sdk.js
(function(window){
class DataCaptureSdk{
constructor(options = {}){
// 用户uid
this.uid = '';
// 上报数据地址的baseUrl
this.url = options.url;
// 页面开始加载时间
this.pageLoadStartTime = options.time;
// 点击事件列表
this.clickEventList = [];
// sdk初始加载
this._sdk_init_loading();
// 页面加载完成时(该阶段可根据实际项目加载完成的时刻处去执行)
this._sdk_page_loaded();
// 页面卸载时
this._sdk_page_beforeunload();
// 错误捕获
this._sdk_catch_error();
}
// 设置uid的方法
_get_uid(uid){
this.uid = uid;
}
// 上报数据的方法
_post(url, params, _call, async=true){
const report_url = `${this.url}/api/${url}`;
const data = {
...params,
uid: this.uid,
host: location.host,
href: location.href,
time: Date.now(),
}
let xhr = new XMLHttpRequest();
xhr.open("POST", url, async);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4 && xhr.status === 200) {
// 具体看后端返回数据结构
const res = JSON.parse(xhr.response).data);
if (_call != undefined) {
_call(res)
}
}
};
xhr.send(data);
}
// 针对页面跳转、页面关闭时数据的上报
_send_beacon_post(url, params){
if (navigator && 'function' === typeof navigator.sendBeacon) {
const data = {
...params,
uid: this.uid || "",
host: location.host,
href: location.href,
time: Date.now(),
}
const data = new FormData();
data.append('data', data);
navigator.sendBeacon(`${this.url}/api/${url}`, data)
}
}
// sdk初始加载
_sdk_init_loading(){
let _self = this;
const ua = navigator.userAgent;
// ...
// 可以从ua获取用户的设备类型、设备品牌、操作系统、浏览器等信息
const params = {
// device_type
// device_name
// device_system
// browser
language: navigator.language, // 语言
network: navigator?.connection?.effectiveType || 'unknown', // 网络状态(safari没有这个属性)
screen_size: window.innerWidth +'_'+ window.innerHeight, // 屏幕的宽度和高度
}
// 初次上报数据
this.post('sdk_loaded', params, (res)=>{
console.log(res)
})
// 页面失去焦点监听器
window.onblur = function(){
// ...
}
// 点击事件监听器
window.addEventListener('click', (e) => {
const element = event.target
const eventInfo = {
el_tag_name: element.tagName,
el_id_name: element.id,
el_class_name: element.className,
el_text_content: element.textContent,
el_src: element.src,
click_page_point: [ event.pageX, event.pageY ]
}
_self.clickEventList.push(e)
// ...
})
}
// 页面加载完成时
_sdk_page_loaded(){
const endLoadTime = Date.now();
this.pageLoadEndTime = endLoadTime;
this.userStayStartTime = endLoadTime;
const loaded_time = endLoadTime - this.pageLoadStartTime;
this._post('page_loaded', { loaded_time }, (res) => {
console.log(res);
})
}
// 页面卸载时
_sdk_page_beforeunload(){
let _self = this
// 除了beforeunload监听器,也可以考虑监听路由变化,pagehide等等
window.addEventListener('beforeunload', function(){
// 用户停留页面时长
const userStayEndTime = Date.now();
_self.userStayEndTime = userStayEndTime;
const stay_duration = userStayEndTime - _self.userStayStartTime;
const params = {
stay_duration: stay_duration, // 用户停留该页面时长
click_info_list: _self.clickEventList, // 点击事件列表
}
_self._send_beacon_post('page_unload', params);
})
}
_sdk_catch_error(){
let _self = this
// 捕获未处理的Promise拒绝
window.addEventListener('unhandledrejection', (event) => {
const errorInfo = {
error_type: 'Promise error',
message: event.reason ? event.reason.message : '',
stack: event.reason ? event.reason.stack : '',
};
_self.catchError(errorInfo);
})
// 捕获资源加载错误
window.addEventListener('error', (event) => {
if (event.target && (event.target.src || event.target.href)) {
const errorInfo = {
error_type: 'Resource load error',
message: 'Resource Load Error',
target: event.target.tagName,
source: event.target.src || event.target.href,
};
_self.catchError(errorInfo);
}
}, true)
}
catchError(errorInfo){
this._post('error', errorInfo, (res) => {
console.log(res);
})
}
}
const scriptTag = document.currentScript;
const dataCaptureSdk = new DataSDKCapture({
time: Date.now(),
url: scriptTag.getAttribute('url'),
// ...其他参数
})
window.dataCaptureSdk = dataCaptureSdk;
})(window)
使用:
<!-- index.html -->
<head>
<script src="/src/sdk.js" url="http://xxx.xxx.xxx.xxx:3001"></script>
</head>