# 如何通过Vue自定义指令实现前端埋点详析

> 作者/来源: UCloud 运营管理员
> 发布时间: 2022-09-14T14:57:00.000Z
> 分类: 解决方案
> 标签: AI, 前端
> 原文链接: http://117.50.162.249:3000/yun/articles/2552

---

# 如何通过Vue自定义指令实现前端埋点详析

> 来源: https://www.ucloud.cn/yun/127720.html
> 作者: 3403771864
> 发布日期: 发布于2022-09-14 22:57

获取用户的交互习惯及喜好，进一步提升转化率，可以在之前的埋点方案实现中，都是在具体的按钮或者图片被点击或者被曝光时主动通过事件去上报埋点。但这种方法适合在埋点比较少时还行的项目，遇见项目中需要大量埋点时，添加的代码就太多了，就会埋点逻辑与业务逻辑的高耦合。

　　由此需要换种方式。我先给大家普及下埋点上报方式都有哪些？

　　手动埋点

　　可视化埋点

　　无痕埋点

　　手动埋点，顾名思义就是纯手动写代码，调用埋点 SDK 提供的函数，在需要埋点的业务逻辑中添加对应方法，上报埋点数据。这种也是之前一直在使用的方法。

　　可视化埋点是指通过可视化系统配置埋点，这种方式接触的不是很多，就不展开说了。

　　无痕埋点，也叫自动埋点、全埋点。即对全局所有事件和页面加载周期进行拦截埋点。

　　一般对哪些数据做埋点？

　　为了达到数据分析，便于后续的运营及产品策略调整的目的，一般需要对以下几点做埋点统计：

　　页面埋点：统计用户进入或者离开页面的信息，如页面浏览次数（pv）、浏览页面人数（uv）、页面停留时长、设备信息等

　　点击埋点：统计用户在页面浏览过程中触发的点击事件，如按钮、导航或者图片的点击次数

　　曝光埋点：统计具体元素是否得到有效曝光

　　需求分析

　　添加埋点的理想化方案是：

　　1、埋点与业务尽量分离清楚，两个业务独立

　　2、减少不对业务代码有侵入

　　3、简约规范，收口要统一处理埋点逻辑

　　由于项目是Vue开发的，所以考虑使用自定义指令的方式来完成埋点上报。为什么会选择自定义指令是为能让业务和埋点解耦。

　　页面埋点框架搭建好，现在主要是点击埋点和曝光埋点。

　　实现思路：简单来说就是在需要埋点的DOM节点挂载特殊属性，通过埋点SDK监听挂载了相应属性对应的事件，造成事件触发时进行埋点数据上报。

　　那如何监听？

　　对于点击事件，我们可以采用addEventListener来监听click事件。这很简单。

　　对于元素的曝光就稍微有点麻烦了。

　　首先我们来看一下为什么需要监测曝光：

![](https://ucloud-blog.cn-bj.ufileos.com/articles/127720/images/127720_000.jpg)

　　还需增加衡量用户对产品的兴趣程度，需要统计计算区域的点击率（点击次数/曝光次数），要考虑到点击率的准确性，保证用户真正的浏览到了这些产品才可以。

　　那么怎么判断元素出现在页面的可视区域呢？

　　以往的思路：监听滚动事件就是通过getBoundingClientRect()方法计算监测区域与视窗的位置，然后判断出元素是否出现在页面的可视区域内。可由于scroll事件的频繁触发，实可能性很大。

　　基于此，浏览器特意为我们打造了一个Intersection ObserverAPI，把性能相关的细节都处理掉，让开发者只关心业务逻辑即可：

![](https://ucloud-blog.cn-bj.ufileos.com/articles/127720/images/127720_001.jpg)

　　由于用户浏览页面的不确定性，还必须要避免重复的曝光行为。这个在曝光之后，移除观察即可。

　　代码实现

　　上面的需求分析还是比较抽象，下面让我们结合代码来看一下最终的实现。

　　Click 类封装

　　点击事件的处理相对比较简单，每次点击触发数据上报即可：

```
　　// src/directives/track/click.js
　　import { sendUBT } from "../../utils/ctrip"
　　export default class Click {
　　add(entry) {
　　// console.log("entry", entry);
　　const traceVal = entry.el.attributes["track-params"].value
　　const traceKey = entry.el.attributes["trace-key"].value
　　const { clickAction, detail } = JSON.parse(traceVal)
　　const data = {
　　action: clickAction,
　　detail,
　　}
　　entry.el.addEventListener("click", function() {
　　console.log("上报点击埋点", JSON.parse(traceVal))
　　console.log("埋点key", traceKey)
　　sendUBT(traceKey, data)
　　})
　　}
　　}
```

　　Exposure 类封装

　　曝光的相对复杂一些。

　　首先通过new IntersectionObserver()实例化一个全局\_observer，如果得到有效曝光的（这里当元素出现一半以上则进行曝光），就去获取 DOM 节点上的trace-key（埋点 key）和track-params（埋点 value）。

```
　　// src/directives/track/exposure.js
　　import "intersection-observer"
　　import { sendUBT } from "../../utils/ctrip"
　　// 节流时间调整，默认100ms
　　IntersectionObserver.prototype["THROTTLE_TIMEOUT"] = 300
　　export default class Exposure {
　　constructor() {
　　this._observer = null
　　this.init()
　　}
　　init() {
　　const self = this
　　// 实例化监听
　　this._observer = new IntersectionObserver(
　　function(entries, observer) {
　　entries.forEach((entry) => {
　　// 出现在视窗内
　　if (entry.isIntersecting) {
　　// 获取参数
　　// console.log("埋点节点", entry.target.attributes);
　　const traceKey = entry.target.attributes["trace-key"].value
　　const traceVal = entry.target.attributes["track-params"].value
　　console.log("traceKey", traceKey)
　　console.log("traceVal", traceVal)
　　const { exposureAction, detail } = JSON.parse(traceVal)
　　const data = {
　　action: exposureAction,
　　detail,
　　}
　　// 曝光之后取消观察
　　self._observer.unobserve(entry.target)
　　self.track(traceKey, data)
　　}
　　})
　　},
　　{
　　root: null,
　　rootMargin: "0px",
　　threshold: 0.5, // 元素出现面积，0 - 1，这里当元素出现一半以上则进行曝光
　　}
　　)
　　}
　　/**
　　* 元素添加监听
　　*
　　* @param {*} entry
　　* @memberof Exposure
　　*/
　　add(entry) {
　　this._observer && this._observer.observe(entry.el)
　　}
　　/**
　　* 埋点上报
　　*
　　* @memberof Exposure
　　*/
　　track(traceKey, traceVal) {
　　// console.log("曝光埋点", traceKey, JSON.parse(traceVal));
　　sendUBT(traceKey, traceVal)
　　}
　　}
```

　　指令封装

　　有了点击和曝光类，下一步就是 Vue 指令的封装了，也是之所以能实现半自动埋点的核心。

　　这里存在一个场景就是对于同一个按钮或者图片，同时存在既需要点击埋点又需要曝光埋点的场景。所以在指令的设计时支持了多带带传入和同时传入的场景：

　　v-track:click|exposure

　　v-track:exposure

```
　　// src/directives/track/index.js
　　import Vue from "vue"
　　import Click from "./click"
　　import Exposure from "./exposure"
　　// 实例化曝光和点击
　　const exp = new Exposure()
　　const cli = new Click()
　　Vue.directive("track", {
　　bind(el, binding) {
　　// 获取指令参数
　　const { arg } = binding
　　arg.split("|").forEach((item) => {
　　// 点击
　　if (item === "click") {
　　cli.add({ el })
　　} else if (item === "exposure") {
　　exp.add({ el })
　　}
　　})
　　},
　　})
```

　　同时需要在src/index.js引入即可：

　　1

```
　　import "./directives/track"
```

　　使用

　　在需要埋点的地方使用也是很简单的：

```
　　<img
　　ref="imageDom"
　　trace-key="o_img"
　　v-track:click|exposure
　　:track-params="
　　JSON.stringify({
　　exposureAction: 's_pictures',
　　clickAction: 'c_pictures',
　　detail: {
　　value: '测试',
　　},
　　})
　　"
　　/>
```

　　不足

　　Vue自定义指令只是一个简单封装，业务代码和埋点代码达到解耦程度，这样埋点的开发成本还是维护成本都降低了很多。

　　现在还有三个问题：曝光时频次很高，是否可以考虑批量上报？用户访问一半页面，突然切出，之后又重新进入，这种情况埋点又该如何上报？用户设备不支持Intersection ObserverAPI 情况，是否要考虑向下兼容？

　　其实方式方法都在不断的优化中，相信可以有更多完美的优化。