如何通過(guò)Vue自定義指令實(shí)現(xiàn)前端埋點(diǎn)詳析
前言
在營(yíng)銷活動(dòng)中,通過(guò)埋點(diǎn)可以獲取用戶的喜好及交互習(xí)慣,從而優(yōu)化流程,進(jìn)一步提升用戶體驗(yàn),提高轉(zhuǎn)化率。
在之前的埋點(diǎn)方案實(shí)現(xiàn)中,都是在具體的按鈕或者圖片被點(diǎn)擊或者被曝光時(shí)主動(dòng)通過(guò)事件去上報(bào)埋點(diǎn)。這種方法在項(xiàng)目中埋點(diǎn)比較少時(shí)還行,一旦項(xiàng)目中需要大量埋點(diǎn)時(shí),不可避免的要添加很多業(yè)務(wù)代碼。也很大程度上造成了埋點(diǎn)邏輯與業(yè)務(wù)邏輯的高耦合。
為了改造這種情況,我們對(duì)于原有的埋點(diǎn)方式做了一些小改進(jìn),使得埋點(diǎn)效率得到了極大提升。
在闡述我們的埋點(diǎn)改造之前,有必要對(duì)埋點(diǎn)的一些常識(shí)做下簡(jiǎn)單的了解。
埋點(diǎn)上報(bào)方式都有哪些?
要知道埋點(diǎn)的類型有很多,上報(bào)的方式也是五花八門。前端常見的埋點(diǎn)方法有三種:
- 手動(dòng)埋點(diǎn)
- 可視化埋點(diǎn)
- 無(wú)痕埋點(diǎn)
手動(dòng)埋點(diǎn),顧名思義就是純手動(dòng)寫代碼,調(diào)用埋點(diǎn) SDK 提供的函數(shù),在需要埋點(diǎn)的業(yè)務(wù)邏輯中添加對(duì)應(yīng)方法,上報(bào)埋點(diǎn)數(shù)據(jù)。這種也是之前一直在使用的方法。
可視化埋點(diǎn)是指通過(guò)可視化系統(tǒng)配置埋點(diǎn),這種方式接觸的不是很多,就不展開說(shuō)了。
無(wú)痕埋點(diǎn),也叫自動(dòng)埋點(diǎn)、全埋點(diǎn)。即對(duì)全局所有事件和頁(yè)面加載周期進(jìn)行攔截埋點(diǎn)。
一般對(duì)哪些數(shù)據(jù)做埋點(diǎn)?
為了達(dá)到數(shù)據(jù)分析,便于后續(xù)的運(yùn)營(yíng)及產(chǎn)品策略調(diào)整的目的,一般需要對(duì)以下幾點(diǎn)做埋點(diǎn)統(tǒng)計(jì):
- 頁(yè)面埋點(diǎn):統(tǒng)計(jì)用戶進(jìn)入或者離開頁(yè)面的信息,如頁(yè)面瀏覽次數(shù)(pv)、瀏覽頁(yè)面人數(shù)(uv)、頁(yè)面停留時(shí)長(zhǎng)、設(shè)備信息等
- 點(diǎn)擊埋點(diǎn):統(tǒng)計(jì)用戶在頁(yè)面瀏覽過(guò)程中觸發(fā)的點(diǎn)擊事件,如按鈕、導(dǎo)航或者圖片的點(diǎn)擊次數(shù)
- 曝光埋點(diǎn):統(tǒng)計(jì)具體元素是否得到有效曝光
需求分析
本文是基于最近項(xiàng)目中添加埋點(diǎn)的需求,我們需要的一種理想化方案是:
- 埋點(diǎn)與業(yè)務(wù)盡量分離,埋點(diǎn)邏輯更應(yīng)該是獨(dú)立于業(yè)務(wù)的
- 盡量不對(duì)業(yè)務(wù)代碼有侵入
- 約定規(guī)范,通過(guò)統(tǒng)一收口來(lái)處理埋點(diǎn)邏輯
由于項(xiàng)目是Vue開發(fā)的,所以考慮使用自定義指令的方式來(lái)完成埋點(diǎn)上報(bào)。選擇自定義指令的原因也是因?yàn)樗芤欢ǔ潭壬夏茏寴I(yè)務(wù)和埋點(diǎn)解耦。
頁(yè)面埋點(diǎn)在框架層面已經(jīng)幫我們做掉了,這里主要關(guān)心的是點(diǎn)擊埋點(diǎn)和曝光埋點(diǎn)。
實(shí)現(xiàn)思路其實(shí)也很清晰:在需要埋點(diǎn)的DOM節(jié)點(diǎn)掛載特殊屬性,通過(guò)埋點(diǎn)SDK監(jiān)聽掛載了相應(yīng)屬性對(duì)應(yīng)的事件,在事件觸發(fā)時(shí)進(jìn)行埋點(diǎn)數(shù)據(jù)上報(bào)。
那么問(wèn)題來(lái)了,怎么監(jiān)聽呢?
對(duì)于點(diǎn)擊事件,我們可以采用addEventListener來(lái)監(jiān)聽click事件。這很簡(jiǎn)單。
對(duì)于元素的曝光就稍微有點(diǎn)麻煩了。
首先我們來(lái)看一下為什么需要監(jiān)測(cè)曝光:

為了衡量用戶對(duì)產(chǎn)品的興趣程度,需要計(jì)算區(qū)域的點(diǎn)擊率(點(diǎn)擊次數(shù)/曝光次數(shù))。為了保證點(diǎn)擊率的準(zhǔn)確性,我們必須保證用戶真正的瀏覽到了這些產(chǎn)品(就比如上圖中最下方的機(jī)酒產(chǎn)品區(qū)域,由于需要滾動(dòng)頁(yè)面,用戶才有可能看到這一區(qū)域)。
那么怎么判斷元素出現(xiàn)在頁(yè)面的可視區(qū)域呢?
按照以往的做法:監(jiān)聽滾動(dòng)事件,通過(guò)getBoundingClientRect()方法計(jì)算監(jiān)測(cè)區(qū)域與視窗的位置,然后判斷元素是否出現(xiàn)在頁(yè)面的可視區(qū)域內(nèi)。但是由于scroll事件的頻繁觸發(fā),性能問(wèn)題很大。
基于此,瀏覽器特意為我們打造了一個(gè)Intersection ObserverAPI,把性能相關(guān)的細(xì)節(jié)都處理掉,讓開發(fā)者只關(guān)心業(yè)務(wù)邏輯即可:

由于用戶瀏覽頁(yè)面的不確定性,還必須要避免重復(fù)的曝光行為。這個(gè)在曝光之后,移除觀察即可。
代碼實(shí)現(xiàn)
上面的需求分析還是比較抽象,下面讓我們結(jié)合代碼來(lái)看一下最終的實(shí)現(xiàn)。
Click 類封裝
點(diǎn)擊事件的處理相對(duì)比較簡(jiǎn)單,每次點(diǎn)擊觸發(fā)數(shù)據(jù)上報(bào)即可:
// 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("上報(bào)點(diǎn)擊埋點(diǎn)", JSON.parse(traceVal))
console.log("埋點(diǎn)key", traceKey)
sendUBT(traceKey, data)
})
}
}Exposure 類封裝
曝光的相對(duì)復(fù)雜一些。
首先通過(guò)new IntersectionObserver() 實(shí)例化一個(gè)全局_observer,如果得到有效曝光的(這里當(dāng)元素出現(xiàn)一半以上則進(jìn)行曝光),就去獲取 DOM 節(jié)點(diǎn)上的trace-key(埋點(diǎn) key)和track-params(埋點(diǎn) value)。
// src/directives/track/exposure.js
import "intersection-observer"
import { sendUBT } from "../../utils/ctrip"
// 節(jié)流時(shí)間調(diào)整,默認(rèn)100ms
IntersectionObserver.prototype["THROTTLE_TIMEOUT"] = 300
export default class Exposure {
constructor() {
this._observer = null
this.init()
}
init() {
const self = this
// 實(shí)例化監(jiān)聽
this._observer = new IntersectionObserver(
function(entries, observer) {
entries.forEach((entry) => {
// 出現(xiàn)在視窗內(nèi)
if (entry.isIntersecting) {
// 獲取參數(shù)
// console.log("埋點(diǎn)節(jié)點(diǎn)", 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, // 元素出現(xiàn)面積,0 - 1,這里當(dāng)元素出現(xiàn)一半以上則進(jìn)行曝光
}
)
}
/**
* 元素添加監(jiān)聽
*
* @param {*} entry
* @memberof Exposure
*/
add(entry) {
this._observer && this._observer.observe(entry.el)
}
/**
* 埋點(diǎn)上報(bào)
*
* @memberof Exposure
*/
track(traceKey, traceVal) {
// console.log("曝光埋點(diǎn)", traceKey, JSON.parse(traceVal));
sendUBT(traceKey, traceVal)
}
}指令封裝
有了點(diǎn)擊和曝光類,下一步就是 Vue 指令的封裝了,也是之所以能實(shí)現(xiàn)半自動(dòng)埋點(diǎn)的核心。
這里存在一個(gè)場(chǎng)景就是對(duì)于同一個(gè)按鈕或者圖片,同時(shí)存在既需要點(diǎn)擊埋點(diǎn)又需要曝光埋點(diǎn)的場(chǎng)景。所以在指令的設(shè)計(jì)時(shí)支持了單獨(dú)傳入和同時(shí)傳入的場(chǎng)景:
v-track:click|exposurev-track:exposure
// src/directives/track/index.js
import Vue from "vue"
import Click from "./click"
import Exposure from "./exposure"
// 實(shí)例化曝光和點(diǎn)擊
const exp = new Exposure()
const cli = new Click()
Vue.directive("track", {
bind(el, binding) {
// 獲取指令參數(shù)
const { arg } = binding
arg.split("|").forEach((item) => {
// 點(diǎn)擊
if (item === "click") {
cli.add({ el })
} else if (item === "exposure") {
exp.add({ el })
}
})
},
})同時(shí)需要在src/index.js引入即可:
import "./directives/track"
使用
在需要埋點(diǎn)的地方使用也是很簡(jiǎn)單的:
<img
ref="imageDom"
trace-key="o_img"
v-track:click|exposure
:track-params="
JSON.stringify({
exposureAction: 's_pictures',
clickAction: 'c_pictures',
detail: {
value: '測(cè)試',
},
})
"
/>不足
通過(guò)Vue自定義指令的一個(gè)簡(jiǎn)單封裝,業(yè)務(wù)代碼和埋點(diǎn)代碼就達(dá)到了一定的解耦,相較之前,無(wú)論是埋點(diǎn)的開發(fā)成本還是維護(hù)成本都降低了很多。
但是這也只是一個(gè)最簡(jiǎn)單的實(shí)現(xiàn),還有很多情況需要考慮:
- 曝光時(shí)頻次很高,是否可以考慮批量上報(bào)?
- 用戶訪問(wèn)一半頁(yè)面,突然切出,之后又重新進(jìn)入,這種情況埋點(diǎn)又該如何上報(bào)?
- 用戶設(shè)備不支持
Intersection ObserverAPI 情況,是否要考慮向下兼容?
鑒于這套埋點(diǎn)方案還在不斷完善中,等后續(xù)完善并在業(yè)務(wù)中落地平穩(wěn)運(yùn)行后。我再分享其中的細(xì)節(jié)給到大家。
總結(jié)
到此這篇關(guān)于如何通過(guò)Vue自定義指令實(shí)現(xiàn)前端埋點(diǎn)的文章就介紹到這了,更多相關(guān)Vue實(shí)現(xiàn)前端埋點(diǎn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue中的vue-print-nb如何實(shí)現(xiàn)頁(yè)面打印
這篇文章主要介紹了vue中的vue-print-nb如何實(shí)現(xiàn)頁(yè)面打印,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue Cli 3項(xiàng)目使用融云IM實(shí)現(xiàn)聊天功能的方法
這篇文章主要介紹了Vue Cli 3項(xiàng)目 使用融云IM實(shí)現(xiàn)聊天功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
說(shuō)說(shuō)Vue.js中的functional函數(shù)化組件的使用
這篇文章主要介紹了說(shuō)說(shuō)Vue.js中的functional函數(shù)化組件的使用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02
Vue+Microapp實(shí)現(xiàn)微前端的示例詳解
這篇文章主要為大家詳細(xì)介紹了如何實(shí)現(xiàn)以vite+vue3+Microapp為主應(yīng)用,以vue2+element為子應(yīng)用的微前端,感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧2023-06-06
Vue2.0基于vue-cli+webpack Vuex的用法(實(shí)例講解)
下面小編就為大家?guī)?lái)一篇Vue2.0基于vue-cli+webpack Vuex的用法(實(shí)例講解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-09-09
Vue 組件參數(shù)校驗(yàn)與非props特性的方法
這篇文章主要介紹了Vue 組件參數(shù)校驗(yàn)與非props特性的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02
詳解Vue中過(guò)度動(dòng)畫效果應(yīng)用
這篇文章主要介紹了詳解Vue中過(guò)度動(dòng)畫效果應(yīng)用,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05
vue實(shí)現(xiàn)個(gè)人信息查看和密碼修改功能
本文通過(guò)實(shí)例代碼給大家介紹了vue實(shí)現(xiàn)個(gè)人信息查看和密碼修改功能,文中給大家補(bǔ)充介紹了vue實(shí)現(xiàn)密碼顯示隱藏切換功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,感興趣的朋友一起看看吧2018-05-05

