为了灵活度和方便,一般项目结合自动埋点
和手动埋点
手动埋点
手动埋点的重点应该在于 声明式 api形式调用, 处理好参数处理和上送即可
而在 Vue 里则可以考虑自定义指令来封装
/**
* 发送请求,使用image标签跨域
* @param url 接口地址
*/
function sendRequest(url) {
if (page.length === 0) {
console.error('请配置有效的page参数', '@burying-point');
return;
}
var img = new Image();
img.src = url;
}
自动埋点
🤔 在 vue3 里用 use 的形式基于手动埋点封装通用级别的埋点也很容易 = = 那还需要自动埋点全收集吗
- 页面切换时,进行采集,即 url 变化时触发的事件;
- 页面失去焦点,得到焦点时,进行采集。即 focus,blur 事件;
- 页面通过浏览器 tab 切换离开,切换回来时,进行采集,即 visibilitychange 事件;
因为单页多页的页面生命周期不同,实现访问量的自动埋点的设计方向就是 url
的变化(单页多页都会变化)
- url变化 =
window.location.origin + window.location.pathname + window.location.hash
这三部分的任一部分变化,即为 url 变化,并不包括window.location.search
这部分的变化; - 在 SPA 中,如果一个页面内有多个 tab(嵌套子路由),当切换 tab 时,开发者也改变他的 url 的
window.location.pathname
,此时也会认为是页面切换,也会产生上报数据(希望的效果是切换页面内的Tab不属于访问量的埋点)
👇 history api
如 pushstate,replacestate
自定义事件,因为 BOM 并没有提供相关的 api 支持 EventListener(传递回调来支持拓展), 需要自行封装使用
/**
* 拼接通用化上报参数
* @param {string} 重写路由事件类型
*/
function resetHistoryFun(type){
// 将原先的方法复制出来
let originMethod = window.history[type]
// 当window.history[type]函数被执行时,这个return出来的函数就会被执行
return function(){
// 执行原先的方法
let rs = originMethod.apply(this, arguments)
// 然后自定义事件
let e = new Event(type.toLocaleLowerCase())
// 将原先函数的参数绑定到自定义的事件上去,原先的是没有的
e.arguments = arguments
// 然后用window.dispatchEvent()主动触发
window.dispatchEvent(e)
return rs;
}
}
window.history.pushState = resetHistoryFun('pushState') // 覆盖原来的pushState方法
window.history.replaceState = resetHistoryFun('replaceState') // 覆盖原来的replaceState方法
window.addEventListener('pushstate', reportBothEvent)
window.addEventListener('replacestate', reportBothEvent)
👆 和微前端重写router的思路一样.... 多页呢?url上参数部分呢? 嵌套子路由呢?
window.addEventListener('focus', ()=>{
console.log('页面得到焦点')
});
window.addEventListener('blur', ()=>{
console.log('页面失去焦点')
})
document.addEventListener('visibilitychange', () => {
if(document.hidden) {
console.log('页面离开')
} else {
console.log('页面进入')
}
})
数据上报方式是 XMLHttpRequest
window.navigator.sendBeacon
基于 h5sdk 上报逻辑架构。
埋点
目前接入lego的埋点方案
TODO:
关键点:
如何获取元素的唯一标记,并可通过该标记反向选择元素 如何随着鼠标的移动,给元素添加浮层 频繁的跨域 iframe 通信,该如何简化 元素的曝光和点击如何同元素标记匹配上报
XPath
// 其中 findIndex 代码如下:
function findIndex(ele, currentTag) {
let nth = 0;
while (ele) {
if (ele.nodeName.toLowerCase() === currentTag) nth += 1;
ele = ele.previousElementSibling;
}
return nth;
}
function getXpath(ele) {
let cur = ele;
const path = [];
while (cur.nodeType === Node.ELEMENT_NODE) {
const currentTag = cur.nodeName.toLowerCase();
const nth = findIndex(cur, currentTag);
path.push(`${(cur.tagName).toLowerCase()}${(nth === 1 ? '' : `[${nth}]`)}`);
cur = cur.parentNode;
}
return `/${path.reverse().join('/')}`;
}
PC 后台埋点
采集到这种 xPath /html/body/div[16]
👆 因为弹窗 div在最外层,顺序随机,这些自动埋点(基于位置)都是无效的
👆 同理 顶部Tab 页签顺序随机,左侧菜单与用户权限有关,顺序随机
此类埋点:
- 需要手动通过标签属性埋点,可以考虑全局自定义ant组件标签属性
- 自动埋点尝试不上送,即有自定义手动埋点属性时不上送 xpath
前置知识-无痕/可视化埋点,xpath
function mouse_down(event) {
var x = event.clientX,
y = event.clientY;
// 根据坐标获取DOM元素
var element = document.elementFromPoint(x, y);
if (!element) {
console.log("error: no element");
}
// 根据DOM元素获取xpath
readXPath(element)
}
xpath: /html/body/div[2]/div[1]/div[1]/span[1]/input[1]/
function readXPath(element) {
count = 1;
result = "";
while (true) {
count += 1;
if (count > 99) {
break;
}
if (element == document.body) {
console.log("/html/body/" + result);
break;
} else {
tag_index = 0;
tmp = element.parentElement;
for (var i = 0; i < tmp.childElementCount; i++) {
if (tmp.children[i].tagName == element.tagName) {
tag_index += 1;
}
if (element == tmp.children[i]) {
result =`${element.tagName.toLowerCase()}[${String(tag_index)}]/${result}`
break;
}
}
element = tmp;
}
}
}
document.onmousedown = mouse_down;
失去埋点数据的可读性
以及布局调整后,埋点数据重新计算,无法整合
https://segmentfault.com/q/1010000043016040
https://developer.mozilla.org/zh-CN/docs/Web/XPath/Introduction_to_using_XPath_in_JavaScript
https://zhuanlan.zhihu.com/p/313016178
https://juejin.cn/post/7185362356652736570