什么是无痕埋点(smart-tracker)
传统的埋点形式,都是手动埋点,在指定的元素上绑定事件,将用户行为信息发送到服务端进行统计。但是当引入无痕埋点的库以后,用户在浏览器里所有行为和操作都会被自动记录下来,并将信息发送到后端进行统计和分析。
我们为什么要做无痕埋点
提高工作效率,解放双手
无痕埋点原理
这里只讲click的无痕埋点原理。
当用户点击了页面上某一个元素,我们要把当前元素到body之间整个dom的路径记录下来,作为这个元素的唯一标识,我们称之为domPath,这个domPath不仅是这个元素唯一标识,还可以通过document.querySelector(domPath)去唯一选择和定位到这个元素,当用户点击一次这个元素,就会将埋点数据上传到服务器,服务器上这个domPath对应的统计数据加一。
无痕埋点代码实现
document.body.addEventListener('click', (event) => {
const eventFix = getEvent(event);
if (!eventFix) {
return;
}
this._handleEvent(eventFix);
}, false)
首先在document的body上监听和绑定全局click事件,捕获用户所有的点击事件。
// 关键代码
const getDomPath = (element, useClass = false) => {
if (!(element instanceof HTMLElement)) {
console.warn('input is not a HTML element!');
return '';
}
let domPath = [];
let elem = element;
while (elem) {
let domDesc = getDomDesc(elem, useClass);
if (!domDesc) {
break;
}
domPath.unshift(domDesc);
if (querySelector(domPath.join('>')) === element || domDesc.indexOf('body') >= 0) {
break;
}
domPath.shift();
const children = elem.parentNode.children;
if (children.length > 1) {
for (let i = 0; i < children.length; i++) {
if (children[i] === elem) {
domDesc += `:nth-child(${i + 1})`;
break;
}
}
}
domPath.unshift(domDesc);
if (querySelector(domPath.join('>')) === element) {
break;
}
elem = elem.parentNode;
}
return domPath.join('>');
}
获取元素唯一标识domPath这段代码是关键。getDomPath函数传入的是用户点击事件的target对象: getDomPath(event.target)。
主要思路是找到当前元素event.target,然后不断的去循环找它的父节点parentNode,将父节点的tagName当做domPath路径上的节点,如果当前元素有id,那就取消所有路径的循环,直接讲id赋值给domPath。
const children = elem.parentNode.children;
if (children.length > 1) {
for (let i = 0; i < children.length; i++) {
if (children[i] === elem) {
domDesc += `:nth-child(${i + 1})`;
break;
}
}
}
domPath.unshift(domDesc);
getDomPath函数中的这段代码意思是在同一级上出现了多个相同tagName元素,那我们要定位到这个event.target这个元素在这一级里的第几个,假设这个div是同一级的第三个,那返回的就是div:nth-child(3),这样就可以在document.querySelector(domPath)里唯一定位到这个元素。
_handleEvent(event) {
const domPath = getDomPath(event.target);
const rect = getBoundingClientRect(event.target);
if (rect.width == 0 || rect.height == 0) {
return;
}
let t = document.documentElement || document.body.parentNode;
const scrollX = (t && typeof t.scrollLeft == 'number' ? t : document.body).scrollLeft;
const scrollY = (t && typeof t.scrollTop == 'number' ? t : document.body).scrollTop;
const pageX = event.pageX || event.clientX + scrollX;
const pageY = event.pageY || event.clientY + scrollY;
const data = {
domPath: encodeURIComponent(domPath),
trackingType: event.type,
offsetX: ((pageX - rect.left - scrollX) / rect.width).toFixed(6),
offsetY: ((pageY - rect.top - scrollY) / rect.height).toFixed(6),
};
this.send(data);
}
这段代码就是得到用户点击某个元素的相对位置的横向位置和竖向位置比例,得到这个位置的值,就可以反向从埋点数据中得到用户点击元素的具体位置,因为是个比例值,所以在反向推导中还能自适应页面大小的改变。
send(data = {}) {
const image = new Image(1, 1);
image.onload = function () {
image = null;
};
image.src = `/?${stringify(data)}`;
}
得到了用户点击的位置信息和唯一标识domPath,就可以将数据发送到服务端进行统计了
用image的src,将数据进行传输。
用image的src有个好处就是轻量,并且还支持跨域,打点基本上都用的这个方法进行发送数据。
结尾
这里讲的仅仅只是无痕埋点的一个简单实现,对整个无痕埋点体系来说,这些只是冰山一角。
真正的无痕埋点,还需要做统计、分析、差量预测、标记策略、智能降噪、可视化无痕、无痕分桶、反向推导热力图、大数据中台等等,涉及到前端、后端、运维、DBA和算法。