200行代码实现前端无痕埋点

摘要:什么是无痕埋点?简单来说,就是当引入无痕埋点的库以后,用户在浏览器里所有行为和操作都会被自动记录下来,并将信息发送到后端进行统计和分析

什么是无痕埋点

简单来说,就是当引入无痕埋点的库以后

用户在浏览器里所有行为和操作都会被自动记录下来

并将信息发送到后端进行统计和分析

传统的埋点形式,都是手动埋点

在指定的元素上绑定事件

将用户行为信息发送到服务端进行统计

假设如果有一万个点需要前端狗去埋,惊喜不惊喜,意外不意外


我们为什么要做无痕埋点

提高工作效率,解放双手

屌丝的双手得到解放以后

就有更多的时间拿双手来取悦自己

嘻嘻


无痕埋点原理

原理很简单,这里只讲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有个好处就是轻量,并且还支持跨域

打点基本上都用的这个方法进行发送数据

作者:第一名的小蝌蚪
微信公众号:前端屌丝
github: https://github.com/airuikun/blog

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_6156