介绍
应用中特定的流程收集一些信息,用来跟踪应用使用的状况,以便后续进一步优化产品或者提供运营的数据支撑。如访问数,访客,停留时常,页面浏览数等
数据收集类型
-
事件数据收集 页面访问量收集 使用监听url变化, 如果是history,则可以监听pushstate 和replacestate
hashchange 页面停留时间收集
页面停留时间,就是记录从访问页面到离开/关闭页面的时间。不管是SPA应用还是非SPA应用,都是记录从页面加载到页面关闭或跳转的时间,
load => 单页面应用 replaceState, pushState, 非单页面应用popstate代码如下:
// 首次进入页面 window.addEventListener("load", () => { // 记录时间 const time = new Date().getTime(); dulation.startTime = time; }); // 单页应用页面跳转(触发 replaceState) window.addEventListener("replaceState", () => { const time = new Date().getTime(); dulation.value = time - dulation.startTime; // 上报 // ..... // 上报后重新初始化 dulation, 以便后续跳转计算 dulation.startTime = time; dulation.value = 0; }); // 单页应用页面跳转(触发 pushState) window.addEventListener("pushState", () => { const time = new Date().getTime(); dulation.value = time - dulation.startTime; // 上报 // ..... // 上报后重新初始化 dulation, 以便后续跳转计算 dulation.startTime = time; dulation.value = 0; }); // 非单页应用跳转触发 popstate window.addEventListener("popstate", () => { const time = new Date().getTime(); dulation.value = time - dulation.startTime; // 上报 // ..... // 上报后重新初始化 dulation, 以便后续跳转计算 dulation.startTime = time; dulation.value = 0; }); // 页面没有任何跳转, 直接关闭页面的情况 window.addEventListener("beforeunload", () => { const time = new Date().getTime(); dulation.value = time - dulation.startTime; // 上报 // ..... // 上报后重新初始化 dulation, 以便后续跳转计算 dulation.startTime = time; dulation.value = 0; });``` ## 异常数据收集 ```window.addEventListener("error", (e) => { console.log("上报", "error"); }); window.addEventListener("unhandledrejection", (e) => { console.log("上报", "unhandledrejection"); }); ```
数据上报
- 数据格式
{ uid; // 用户id title; // 页面标题 url; // 页面的url time; // 触发埋点的时间 ua; // userAgent screen; // 屏幕信息, 如 1920x1080 type // 数据类型,根据触发的不同埋点有不同的类型 data; // 根据不同的type,此处的数据也不同,如 事件数据有元素名称,事件名称、页面停留时间有停留时长.... sdk // sdk 相关信息 }
- 上报方式
一般情下使用
XMLHttpReuqest(axios)或者Fetch,但用其有两个缺陷,一是有跨域问题,二是在页面关闭时会中断正在发送的数据。
考虑到以上几点,一般使用图片来发送数据或者使用navigator.sendBeacon,这里就使用sendBeacon,简单方便。也可以两者结合(如果浏览器不支持sendBeancon,就使用图片的方式)。
function send(data) { const url = `xxxx` if (navigator.sendBeacon) { navigator.sendBeacon(url, JSON.stringify(data)); } else { const imgReq = new Image(); imgReq.src = `${url}?params=${JSON.stringify( data )}&t=${new Date().getTime()}`; } }
sdk实现
// JS 完整代码部分 (function (e) { function wrap(event) { const fun = history[event]; return function () { const res = fun.apply(this, arguments); const e = new Event(event); window.dispatchEvent(e); return res; }; } class TrackingDemo { constructor(options = {}) { // 重写 pushState、replaceState window.history.pushState = wrap("pushState"); window.history.replaceState = wrap("replaceState"); // 上报地址 this.reportUrl = options.reportUrl || ""; this.sdkVersion = "1.0.0"; this._eventList = ["click", "dblclick", "mouseout", "mouseover"]; this._dulation = { startTime: 0, value: 0, }; this._initJSError(); // 初始化事件数据收集 this._initEventHandler(); // 初始化PV统计 this._initPV(); this._initPageDulation(); } setUserId(uid) { this.uid = uid; } _initEventHandler() { this._eventList.forEach((event) => { window.addEventListener(event, (e) => { const target = e.target; const reportKey = target.getAttribute("report-key"); if (reportKey) { this._report("event", { tagName: e.target.nodeName, tagText: e.target.innerText, event, }); } }); }); } _initPV() { window.addEventListener("pushState", (e) => { this._report("pv", { type: "pushState", referrer: document.referrer, }); }); window.addEventListener("replaceState", (e) => { this._report("pv", { type: "replaceState", referrer: document.referrer, }); }); window.addEventListener("hashchange", () => { this._report("pv", { type: "hashchange", referrer: document.referrer, }); }); } _initPageDulation() { let self = this; function initDulation() { const time = new Date().getTime(); self._dulation.value = time - self._dulation.startTime; self._report("dulation", { ...self._dulation, }); self._dulation.startTime = time; self._dulation.value = 0; } // 首次进入页面 window.addEventListener("load", () => { // 记录时间 const time = new Date().getTime(); this._dulation.startTime = time; }); // 单页应用页面跳转(触发 replaceState) window.addEventListener("replaceState", () => { initDulation(); }); // 单页应用页面跳转(触发 pushState) window.addEventListener("pushState", () => { initDulation(); }); // 非单页应用跳转触发 popstate window.addEventListener("popstate", () => { initDulation(); }); // 页面没有任何跳转, 直接关闭页面的情况 window.addEventListener("beforeunload", () => { initDulation(); }); } _initJSError() { window.addEventListener("error", (e) => { this._report("error", { message: e.message, }); }); window.addEventListener("unhandledrejection", (e) => { this._report("error", { message: e.reason, }); }); } // 用户可主动上报 reportTracker(data) { this._report("custom", data); } _getPageInfo() { const { width, height } = window.screen; const { userAgent } = navigator; return { uid: this.uid, title: document.title, url: window.location.href, time: new Date().getTime(), ua: userAgent, screen: `${width}x${height}`, }; } _report(type, data) { const reportData = { ...this._getPageInfo(), type, data, sdk: this.sdkVersion, }; if (navigator.sendBeacon) { navigator.sendBeacon(this.reportUrl, JSON.stringify(reportData)); } else { const imgReq = new Image(); imgReq.src = `${this.reportUrl}?params=${JSON.stringify( reportData )}&t=${new Date().getTime()}`; } } } e.TrackingDemo = TrackingDemo; })(window); // HTML 代码部分 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <button report-key="button">按钮</button> </body> <script src="./tackerDemo.js"></script> <script> const trackingDemo = new TrackingDemo() </script> </html>
工程化
参考文档