vue宣告式埋點實踐

衛辰9發表於2019-04-09

本文介紹了針對vue的宣告式埋點,如何做vue的宣告式埋點,使用方便,與指令碼邏輯解耦,目前應用在斑馬和捕手等眾多平臺

埋點方案的嘗試

目前現有的埋點方案有命令式埋點、宣告式埋點、視覺化埋點、無痕埋點

  • 命令式埋點是用比較常見的方式,在使用者產生行為的地方使用js方法進行資料上報,優點是埋點方式比較簡單,缺點是與業務耦合度較高
  • 宣告式埋點是在具體DOM元素上進行資料繫結,只需元件開發人員在sdk上制定埋點方案,業務開發人員設定資料即可,優點是埋點程式碼與具體的互動和業務邏輯解耦
  • 視覺化埋點是通過視覺化工具配置埋點,需要另外配套一個平臺控制埋點的埋入,優點是自動生成埋點程式碼嵌入到頁面中,減輕業務開發人員的埋點負擔,目前做得好得例如Mixpanel
  • 無埋點是前端自動採集全部事件,上報埋點資料,由後端來過濾和計算出有用的資料,優點是完全無需業務參與,完全與業務解耦,目前比較流行的例如GrowingIO

本埋點應用在電商平臺,要埋的點比較繁多善變,使用宣告式埋點是比較合適的

宣告式埋點

vue中提供了自定義指令,在模版元素中插入自定義指令,可以跟蹤元素的結構變化,跟蹤資料變化,比DOM的data屬性更加方便抓取跟蹤

按照官方文件一個指令定義物件提供如下幾個鉤子函式:

  • bind:只呼叫一次,指令第一次繫結到元素時呼叫。在這裡可以進行一次性的初始化設定。
  • inserted:被繫結元素插入父節點時呼叫 (僅保證父節點存在,但不一定已被插入文件中)。
  • update:所在元件的 VNode 更新時呼叫,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前後的值來忽略不必要的模板更新。
  • componentUpdated:指令所在元件的 VNode 及其子 VNode 全部更新後呼叫。
  • unbind:只呼叫一次,指令與元素解綁時呼叫。

指令鉤子函式會被傳入以下引數:

  • el:指令所繫結的元素,可以用來直接操作DOM。
  • binding:一個物件,包含指令的指令名、繫結值、表示式等。

其中bind和update是比較重要的兩個鉤子函式,它能跟蹤DOM的出現和變化,獲取資料並資料上報埋點,具體的程式碼如下:

Vue.directive('bury-click', {
  bind(el, binding) {
    _ga.buryClick(el, binding)
  },
  update(el, binding) {
    _ga.buryClickUpdate(el, binding)
  },
  unbind(el) {
    _ga.cancelBuryClick(el)
  }
})
複製程式碼

整個指令的生命週期以及埋點的資料收集方式如下圖所示

宣告式埋點

實現方式

根據現有的埋點需求,主要實現了頁面曝光、坑位曝光、坑位點選三種形式

  • 頁面曝光:對於vue的單頁應用來說,頁面的切換跟蹤就是路由的變化,但是在統一的路由鉤子放置每個頁面不同的資料不太現實,於是就考慮在每個view頁面根部埋下頁面曝光資料,只要使用者一訪問該頁面,就觸發頁面曝光
  • 坑位曝光:坑位的曝光實際就是一個DOM出現在使用者視野範圍內進行抓取,需要藉助全域性滾動(scroll)事件,使用節流的方式檢測目標坑位是否在可視範圍內,在的話就觸發坑位曝光
  • 坑位點選:坑位的點選需要對目標坑位進行點選事件繫結,在不影響目標坑位原有事件基礎上,繫結點選的埋點上報,在元素unbind的時候解除對應點選事件

針對以上三種情況,不入侵業務的指令碼邏輯,制定了三個自定義指令v-page-exposurev-exposurev-bury-click

  • v-page-exposure
<div class="root" v-page-exposure="'1.1.0.0'"></div>
複製程式碼

如程式碼指令所示,a.b.c.d分別為應用程式碼.頁面程式碼.模組程式碼.坑位程式碼,就是埋點統計需要的程式碼資訊,把資訊收集上報即可。需要注意的是指令資訊是一個js表示式,如果不加引號會預設執行js變成一個物件取值報錯。

  • v-exposure
<div v-exposure="{gcms:[{type:1, uri:22},{type:2, uri:41}],seq:[1],spm:'1.0.0.0'}"></div>
複製程式碼

坑位曝光收集的是一個js物件,包含一些坑位的程式碼資訊、位置資訊、商品資訊、跳轉資訊等。坑位曝光采用實時上報,且曝光一次就不再曝光,所以每次bind的時候把所有曝光坑位收集在一個陣列,坑位出現在使用者可視範圍內一次就上報彈出陣列,具體的程式碼如下:

buryExposure(el, binding) {
  this.exposureList.push({
    el,
    binding: binding.value
  })
}

exposure() {
  if (this.exposureList.length !== 0) {
    for (let i = 0; i < this.exposureList.length; i++) {
      let item = this.exposureList[i]
      const currTop = item.el.getBoundingClientRect().top + window.pageYOffset
      const scrollTop = document.documentElement.scrollTop
      // 修正5畫素的偏差
      if (currTop + 5 >= scrollTop && currTop < (scrollTop + window.innerHeight)) {
        // 傳送曝光事件
        if (isWebview) {
          this.appAction()
        } else {
          this.action()
        }
        this.exposureList.splice(i, 1)
        i--
      }
    }
  }
}
複製程式碼
  • v-bury-click
<div v-bury-click="JSON.stringify({gcms:[{type:1, uri:22},{type:2, uri:41}],spm:'1.0.0.0'})"></div>
複製程式碼

坑位點選也是一個js物件,包含坑位的各種資訊,在頁面載入的時候繫結點選事件,使用者點選的時候上報埋點。在實際使用過程中埋點需要跟蹤最新的資料資訊,比如在加入購物車的環節中,加入購物車的按鈕是同一個不變,但下一次選擇商品的資訊可能發生改變,所以每次點選按鈕需要傳遞最新的埋點資訊。由於指令資訊是一個js表示式,物件是一個引用型別,改變物件的值不會觸發update鉤子函式,所以需要JSON字串化一下。具體的程式碼如下:

buryClick(el, binding) {
  el.addEventListener('click', this.click.bind(this, el))
}

click(el) {
  ...
  this.gaData.ext = encodeURIComponent(JSON.stringify({ eventId: '2', ...value }))
  if (isWebview) {
    this.appAction()
  } else {
    this.action()
  }
}
複製程式碼

附加資訊

除了埋點點位資訊之外,還需要一些常用的基礎資訊,比如使用者資訊、系統資訊、會話資訊、端資訊等等,都是可以通過頁面初次載入的時候收集。另外還需要驗證埋點請求,要在業務請求頭中埋入埋點的資訊,這些資訊只存在於埋點的快取中,於是就重寫XHR和fetch請求,在每次傳送請求前加入需要的埋點資訊,與業務解耦。

相關文章