【前端埋点】网站自动化埋点方案
前端埋点的核心在于高效地收集、处理和上报用户行为数据。
本文中的埋点代码通过模块化设计和灵活的实现方式,提供了一个高效且易于扩展的前端埋点解决方案。
代码位置
https://gitee.com/zhangkb/example-code/tree/master/h5-track
整体架构设计
代码的整体架构基于一个核心类 Tracker
,它负责初始化基础数据、监听用户行为事件、处理事件数据,并将数据推送到数据队列中。
Tracker
类通过与其他辅助模块(如 TrackerQueue
、工具函数等)协作,实现了完整的埋点流程。
export class Tracker {
basicParams = {
user_info: {},
device_info: {},
};
customParams = {};
customFnMap = {};
trackerQueue = null;
areaVisitEntryMap = {};
elementViewMap = {};
counterMap = {};
counterKey = "count";
elementViewobserver = null;
destroyFlag = false;
constructor(pageType, { customParams = {}, customFnMap = {} } = {}) {
this.basicParams.page_type = pageType;
this.setCustomFnMap(customFnMap);
this.setCustomParams(customParams);
this.init();
}
async init() {
this.initTrackerQueue();
this.initBasicParams();
await this.initBasicParamsAsync();
this.initEvent();
}
}
Tracker
类的作用
- 作为埋点的核心类,
Tracker
负责管理埋点的生命周期,从初始化基础数据到监听事件,再到数据上报。 - 它通过
TrackerQueue
将数据批量发送到后端,避免频繁的网络请求,从而提高性能。 - 提供了灵活的扩展机制,允许通过
customParams
和customFnMap
添加自定义参数。
这种模块化的设计使得代码结构清晰,易于维护和扩展。
基础信息初始化
在前端埋点中,基础信息的初始化是至关重要的一步,它为后续的数据上报提供了上下文信息。代码中通过 initBasicParams
和 initBasicParamsAsync
方法完成了这一任务。
initBasicParams() {
const deviceInfo = getDeviceInfo();
// 页面信息
this.basicParams["page_url"] = window.location.pathname;
this.basicParams["page_params"] = getQuery();
// 会话 ID,会话内唯一
this.basicParams["session_id"] = getSessionId();
// 设备信息
this.basicParams["device_info"] = deviceInfo;
}
async initBasicParamsAsync() {
const userInfo = await UserService.getUserInfo();
// 用户信息
if (userInfo) {
this.basicParams["user_info"]["customer_id"] = userInfo.openId;
}
}
同步初始化(
initBasicParams
)- 页面信息:通过
window.location.pathname
和getQuery
获取当前页面的 URL 和查询参数,这些信息有助于区分不同的页面和用户行为。 - 会话 ID:通过
getSessionId
获取会话 ID,用于标识用户在同一会话中的行为。 - 设备信息:通过
getDeviceInfo
获取设备信息,如屏幕分辨率、浏览器类型等,这些信息有助于分析用户设备的多样性。
- 页面信息:通过
异步初始化(
initBasicParamsAsync
)- 用户信息:通过
UserService.getUserInfo
异步获取用户信息,如用户 ID、登录状态等。这些信息对于分析用户行为和个性化推荐至关重要。
- 用户信息:通过
通过同步和异步结合的方式,代码确保了基础信息的完整性,为后续的数据分析提供了丰富的上下文。
事件监听与处理
前端埋点的核心在于监听和处理各种用户行为事件。代码中实现了多种事件的监听和处理逻辑,包括页面曝光、点击事件、滚动事件、元素曝光与隐藏以及区域访问等。
页面曝光(
pageView
) 页面曝光事件(pageView
)是用户进入页面时触发的第一个事件。代码通过trackPageViewEvent
方法在页面加载时自动触发该事件,记录用户进入页面的时间和页面信息。trackPageViewEvent() { this.trackEvent(TrackEventBackend.pageView); }
这种设计确保了页面曝光数据的完整性和实时性,为后续的页面流量分析提供了基础数据。
点击事件(
click
) 点击事件是用户行为分析中最常见的事件之一。代码通过handleClick
方法实现了对点击事件的监听和处理。async handleClick(event) { const target = event.target; const trackClickNode = findTrackNode(target, TrackEvent.click); if (trackClickNode) { const trackData = await this.getTrackData(trackClickNode, TrackEvent.click); if (trackData) { this.trackClickEvent(trackData.elementName, trackData.params); } } }
当用户点击页面上的某个元素时,
handleClick
方法会通过事件委托捕获点击事件,并通过findTrackNode
方法查找最近的带有埋点属性的元素。如果找到目标元素,代码会调用getTrackData
方法获取埋点数据,并通过trackClickEvent
方法将点击事件数据推送到数据队列中。这种设计不仅提高了代码的复用性,还通过事件委托减少了事件监听器的数量,提高了性能。
滚动事件(
scroll
) 滚动事件用于分析用户在页面上的滚动行为,例如滚动深度。代码通过handleScroll
方法实现了滚动事件的监听。async handleScroll(event) { const totalHeight = document.documentElement.scrollHeight; const viewportHeight = window.innerHeight; const scrollY = window.scrollY; const scrollPercentage = (scrollY / (totalHeight - viewportHeight)) * 100; this.trackEvent(TrackEventBackend.pageScroll, null, { scroll_depth: scrollPercentage }); }
在滚动事件中,代码计算了当前滚动的深度百分比(
scrollPercentage
),并通过trackEvent
方法将滚动深度数据上报到后端。为了防止滚动事件过于频繁触发,代码中可以结合throttle
方法对滚动事件进行节流处理,从而提高性能。元素曝光与隐藏(
elementView
和elementHide
) 元素曝光和隐藏事件用于分析页面上特定元素的可见性。代码通过IntersectionObserver
实现了对元素曝光和隐藏的监听。initListenerElementView() { if (IntersectionObserver) { const nodes = document.querySelectorAll("[data-track-elementview]"); const observer = new IntersectionObserver(this.handleElementView.bind(this)); nodes.forEach((element) => { observer.observe(element); }); this.elementViewobserver = observer; } else { console.error("elementView Error:不支持 IntersectionObserver"); } } handleElementView(entries, observer) { entries.forEach(async (entry) => { const trackData = await this.getTrackData(entry.target, TrackEvent.elementView); const elementName = trackData.elementName; if (trackData) { if (entry.isIntersecting) { this.trackElementViewEvent(elementName, trackData.params); } else if (this.elementViewMap[elementName]) { this.trackElementHideEvent(elementName, trackData.params); } } }); }
在
initListenerElementView
方法中,代码创建了一个IntersectionObserver
实例,并对带有data-track-elementview
属性的元素进行监听。当元素进入视口时,触发elementView
事件;当元素离开视口时,触发elementHide
事件。通过这种方式,代码可以记录元素的曝光时长,为分析用户对页面元素的关注程度提供数据支持。区域访问(
areaVisit
) 区域访问事件用于分析用户在页面特定区域的行为。代码通过handleClick
方法实现了对区域访问事件的监听。const trackAreavisitNode = findTrackNode(target, TrackEvent.areaVisit); if (trackAreavisitNode) { const trackData = await this.getTrackData( trackAreavisitNode, TrackEvent.areaVisit ); if (trackData && !this.areaVisitEntryMap[trackData.elementName]) { trackData.timestamp = Date.now(); this.areaVisitEntryMap[trackData.elementName] = trackData; this.trackEvent( TrackEventBackend.areaVisitEntry, trackData.elementName, trackData.params ); } }
当用户点击某个区域时,代码会检查是否存在
areaVisit
事件,并记录进入和离开该区域的时间。通过这种方式,代码可以计算用户在特定区域的停留时长,从而为分析用户行为模式提供数据支持。
自定义参数与动态参数
在实际的业务场景中,除了基础的埋点数据外,往往还需要添加一些自定义参数来丰富数据的维度。代码通过 customParams
、customFnMap
和 getDynamicParams
方法实现了自定义参数的灵活扩展。
setCustomParams(customParams) {
this.customParams = customParams;
}
setCustomFnMap(customFnMap) {
this.customFnMap = customFnMap;
}
async getTrackData(element, type) {
try {
const elementName = element.getAttribute(`data-track-${type}`);
const dynamicParams = getDynamicParams(element);
const customFnParams = await this.getCustomFnParams(element);
return {
elementName,
params: {
...dynamicParams,
...customFnParams
}
};
} catch (error) {
console.error("获取埋点数据失败 getTrackData:", error);
return null;
}
}
自定义参数(
customParams
和customFnMap
)customParams
是一个对象,用于存储通用的自定义参数。开发者可以在初始化Tracker
实例时传入这些参数,它们会被附加到每一条埋点数据中。customFnMap
是一个函数映射表,允许开发者通过函数动态生成自定义参数。在getCustomFnParams
方法中,代码会根据元素的data-track-fn
属性调用对应的函数,并将返回的结果作为自定义参数。- 这种设计使得埋点数据能够根据具体业务需求灵活扩展,提高了代码的通用性和可扩展性。
动态参数(
getDynamicParams
)- 动态参数是指根据当前页面环境动态生成的参数。代码通过
getDynamicParams
方法从 DOM 元素中提取这些参数。 - 例如,开发者可以在 DOM 元素上添加
data-track-dynamic
属性,getDynamicParams
方法会解析这些属性并将其值作为动态参数附加到埋点数据中。 - 这种设计使得埋点数据能够根据页面的具体情况动态调整,进一步丰富了数据的维度。
- 动态参数是指根据当前页面环境动态生成的参数。代码通过
数据上报机制
数据上报是前端埋点的最后一步,也是至关重要的一步。代码通过 TrackerQueue
和 Http.post
方法实现了数据的上报。
async send(data) {
console.log("埋点数据上报:", data);
if (this.destroyFlag && typeof navigator.sendBeacon === "function") {
const jsonData = JSON.stringify(data);
const blob = new Blob([jsonData], { type: "application/json" });
const result = navigator.sendBeacon("https://api.haoma.cn/event-tracking/hmzj/user-behavior-events/send", blob);
console.log("navigator.sendBeacon 推送数据", result);
return;
}
const response = await Http.post({
url: "https://api.haoma.cn/event-tracking/hmzj/user-behavior-events/send",
headers: {
"content-type": "application/json;charset=utf-8"
},
data: JSON.stringify(data)
});
}
队列管理(
TrackerQueue
)TrackerQueue
是一个数据队列,用于管理埋点数据的存储和上报。它将埋点数据批量发送到后端,减少了网络请求的次数,提高了性能。- 在
trackEvent
方法中,代码将每一条埋点数据推送到TrackerQueue
中。当队列中的数据达到一定数量时,TrackerQueue
会自动触发数据上报。 - 这种设计不仅提高了数据上报的效率,还通过批量发送减少了网络请求的开销。
发送方式
- 在数据上报时,代码根据当前的页面状态选择不同的发送方式。如果页面即将离开(
beforeunload
事件触发),代码会使用navigator.sendBeacon
方法发送数据。 navigator.sendBeacon
是一种高效的异步发送方式,它可以在页面卸载时确保数据被发送到后端,而不会阻塞页面的卸载过程。- 如果页面处于正常状态,代码会使用
Http.post
方法发送数据。通过这种方式,代码确保了数据上报的可靠性和效率。
- 在数据上报时,代码根据当前的页面状态选择不同的发送方式。如果页面即将离开(