埋点圈选功能原理解析

介绍

圈选功能,可理解为是无痕埋点技术的具体应用产品。

使用方式:

1.web 在线网页

圈选功能管理后台上,提供了一个容器加载在线地址,通过点击指定元素的方式,获取元素的特殊标记并上报数据分析系统。数据筛选服务,根据上报的标记将无痕上报的数据进行分类统计。

2.混合 APP

指定的 APP 上开启圈选功能,通过拖拽定位圆点工具,获取对应的容器中加载的 web 页面的元素座标,并上报数据分析系统。数据筛选服务,根据上报的标记将无痕上报的数据进行分类统计。

实现原理

无痕埋点

首先不得不再次强调,圈选功能的基础是无痕埋点技术。圈选功能更容易理解为是一种筛选数据的工具,那么前提就是需要有数据,那么就需要通过无痕埋点技术优先将用户的行为数据都存储在数据仓库中(这里指的是全量上报数据的方式;大流量场景则不适用,可采用后置上报的方式,这里不过多展开)。

无痕埋点简单实现,通过根节点注册监听事件(事件委托),用户在页面中的所有操作行为,都会冒泡到顶层统一分析上报。主要内容包含 1.当前页面的唯一标识。2.用户的操作行为事件。3.交互元素的 xPath 路径,以及元素部分有效信息。4.浏览器系统信息、用户标识、网络等额外信息。

更多信息可查看日志无痕埋点技术

圈选原理实现

通过平台工具加载指定内容模块,平台提供定位元素的工具。工具定位元素后,圈选 SDK 获取指定元素的相关信息,如 xPath 路径以及浏览器系统信息等,通过消息通信机制将指定信息传递到工具平台,并上报标记信息。数据分析后台通过这些上报的标记筛选出有效信息,并可视化展示。

圈选核心代码

1.圈选初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 圈选初始化
*/
Circle.init = function () {
document.addEventListener(
"mouseover",
function (e) {
if (!_circleDisabled && e.isTrusted !== false) {
Circle.drawField(e.target); // 绘制圈选效果
}
},
true
);
document.addEventListener(
"mouseout",
function (e) {
if (!_circleDisabled && e.isTrusted !== false) {
Circle.cleanDrawField(); // 清除圈选效果
}
},
true
);
document.addEventListener(
"click",
function (e) {
// 点击圈选元素,则确定被圈选元素,并上传信息
if (!_circleDisabled) {
if (e.isTrusted !== false) {
Circle.cleanDrawField();
_circleDisabled = true;
// 获取元素节点的所有信息 utmId,path,xpath,content,image
Circle.getNodeFieldInfo(e.target, function (data) {
Messenger.send(0, data, "圈选节点信息"); // 圈选元素信息,通信给工具平台
setTimeout(function () {
_circleDisabled = false;
}, 500);
});
}
// 阻止事件冒泡和浏览器默认行为
window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
e.preventDefault
? e.preventDefault()
: window.event.returnValue == false;
}
},
true
);
};

2.获取圈选元素信息(核心)

UTM方法为无痕埋点 SDK 中的方法。圈选 SDK 与无痕埋点 SDK 做了集成,所以能够直接调用无痕埋点中元素相关信息的获取方法。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 获取节点信息
* @param node 目标节点
* @return 圈选元素信息
*/
Circle.getNodeFieldInfo = function (node) {
return {
utm: UTM.utmID(), // 获取页面定位信息
path: UTM.utmPath(node), // 获取节点 xPath 路径
content: UTM.utmContent(node), // 获取节点相关有效内容
};
};

3.圈选效果绘制

此处提取部分代码内容,仅供参考,也可以有更加优雅的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/**
* 样式格式化
* @param {*} obj 样式对象
*/
function formatClass(obj) {
var arr = [];
for (var i in obj) {
arr.push(i + ":" + obj[i]);
}
return arr.join(";") + ";";
}
/**
* 绘制圈选区块效果
* @param node 圈选节点
* @return node 返回节点
*/
Circle.drawField = function (node) {
node.className += " " + circleClass;
node.setAttribute(
"style",
(node.getAttribute("style") ? node.getAttribute("style") : "") +
formatClass(_circlePlatform == "PC" ? _circle_hoverPC : _circle_hover)
);
return node;
};

/**
* 清理绘制的圈选效果
*/
Circle.cleanDrawField = function () {
var re = new RegExp("\\s*" + circleClass, "gi");
var reStyle = new RegExp(
formatClass(_circlePlatform == "PC" ? _circle_hoverPC : _circle_hover),
"gi"
);
Array.from(document.getElementsByClassName(circleClass)).forEach(function (
node
) {
node.className = node.className.replace(re, "");
var sourceStyle = node.getAttribute("style").replace(reStyle, "");
if (sourceStyle) {
node.setAttribute("style", sourceStyle);
} else {
node.removeAttribute("style");
}
if (!node.className) {
node.removeAttribute("class");
}
});
Array.from(document.getElementsByClassName("circle-mask")).forEach(function (
node
) {
node.parentNode.removeChild(node);
});
};

4.消息通信

主要用了postMessage做通信,既能解决跨域问题,也效率高。(安全性暂不展开讨论,部分代码仅供参考)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// #region 消息通知
var Messenger = Messenger || {};

/**
* 监听消息
* @param {Object} MessageEvent 消息体
* @param {Number} MessageEvent.data.code 状态码,0开启圈选 ,10关闭圈选
* @param {Any} MessageEvent.data.data 数据,PC | APP
* @param {String} MessageEvent.data.message 消息日志
*/
Messenger.listen = function (MessageEvent) {
if (typeof MessageEvent == "object" && MessageEvent.data) {
var res = MessageEvent.data;
if (res.code == 0) {
// 圈选初始化
_circleDisabled = false;
if (res.data == "PC") {
_circlePlatform = "PC";
Circle.init();
} else if (res.data == "APP") {
_circlePlatform = "APP";
Circle.initHybridH5();
}
} else if (res.code == 1) {
// 直接获取站点信息
if (res.data == "PC") {
_circlePlatform = "PC";
Messenger.send(
1,
{
utm: UTM.utmID(),
},
"页面信息"
);
}
} else if (res.code == 2) {
// 直接获取站点信息
if (res.data == "PC") {
_isToolPlatform = true;
var _event = new CustomEvent("isToolPlatform");
if ("dispatchEvent" in window) {
window.dispatchEvent(_event);
}
}
} else if (res.code == 10) {
// 关闭圈选功能
Circle.close();
}
}
};

/**
* 向父级发送消息
* @param code 状态码
* @param data 数据
* @param message 日志
* @param target 目标对象,默认 parent.window
*/
Messenger.send = function (code, data, message, target) {
if (!target) {
target = parent.window;
}
target.postMessage({ code: code, data: data, message: message }, "*");
};

if ("postMessage" in window) {
if ("addEventListener" in window) {
window.addEventListener("message", Messenger.listen, false);
} else if ("attachEvent" in window) {
window.attachEvent("onmessage", Messenger.listen);
}
} else {
window.navigator.Messenger = Messenger.listen;
}

// #endregion

最后

日常开发中,我们一定会遇到或多或少的埋点需求,每一次的埋点需求,也就意味着开发需要做一次发布上线。如果通过无痕埋点+埋点圈选的方式,就能够解放开发同学的双手,同样也保障了系统的稳定性,更重要的是业务运营的相关人员,可以及时的获取到自己想要的数据,而不需要走开发的一系列冗长的流程,以能够应对市场的瞬息万变。

以上为圈选功能的简单实现,目前也大范围的在业务中实践使用,对开发的收益十分感人!感谢大佬们的阅读,不足之处请多多包涵。

文章作者: 方长_beezen
文章链接: https://dongbizhen.com/posts/35522/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 BEEZEN