前端曝光數據埋點——Intersection Observer+vue指令

1、背景介紹

在電商產品中(能夠打開你的淘寶、天貓、京東App),經過對商品的曝光進行數據埋點,就能反推出用戶的行爲和交互習慣,從而優化推薦和搜索算法以及交互,最終的目的固然是爲了增長用戶購買力。 javascript

曝光:商品出如今用戶眼前,也就是瀏覽器視窗,可謂之曝光
html

最直白的兩種方法 前端

這兩種辦法都能用,可是getBoundingClientRect這個API是會引發頁面迴流的,使用不當容易致使性能問題。 vue

基於此,瀏覽器特地爲咱們打造了一個[Intersection Observer API - Web API | MDN],把性能相關的細節都處理掉,讓開發者只關心業務邏輯便可
java



2、Intersection Observer API 

整個曝光打點方案基於[Intersection Observer API - Web API 接口參考 | MDN] node

這個API還比較新,至於兼容性問題可用[IntersectionObserver/polyfill· w3c]解決,本質上也是用getBoundingClientRect計算位置,具體怎麼實現能夠去看看它的源碼
git


3、數據埋點思路

  1. new IntersectionObserver() 實例化一個全局_observer,每一個商品DOM自行把本身加入_observer的觀察列表 (這裏會用Vue的指令來實現) 
  2. 當某個商品DOM進入視窗,收集該商品的信息,存進一個全局數組dotArr中,而後取消對該商品DOM的觀察 
  3. dotArr中取數據進行打點比較簡單 
    •  跑定時器,每隔N秒檢查一次,若是dotArr有數據,就直接上報; 
    • 若是N秒內,dotArr的數據量大於某個量maxNum,不等定時器,直接所有上報
  4. 打點不難,難的是不漏以及不重複上報數據,用戶離開頁面前的邊界數據處理
    • 瀏覽器環境:dotArr同時存一份在localStorage中,同步更新數據(增長或者上報完後清空),若是用戶真的在N秒的間隔內,而數據又不夠最大上報量maxNum就離開了頁面,那麼這批數據就等用戶下次再進頁面時,直接從localStorage中取出來打掉,固然若是這個用戶不再進頁面或者清空了瀏覽器緩存,這一點點數據丟失能夠接受。 
    • 客戶端webview環境:註冊webview關閉的生命鉤子事件(須要客戶端童鞋支持),離開前所有打掉


4、代碼實現

一、封裝Exposure類

// polyfill
import 'intersection-observer';
// 自行封裝數據上報方法,其實就是網絡請求
import { DotData } from './DotData'

// 能夠把節流的時間調大一點,默認是100ms
IntersectionObserver.prototype['THROTTLE_TIMEOUT'] = 300;

export default class Exposure {
  dotDataArr: Array<string>;
  maxNum: number;
  // _observer能夠理解爲觀察者的集合吧
  _observer;
  _timer: number;

  constructor(maxNum = 20) {
    // 當前收集的 還沒有上報的數據 也就是已經進入視窗的DOM節點的數據
    this.dotDataArr = [];
    this.maxNum = maxNum;
    this._timer = 0;
    // 全局只會實例化一次Exposure類,init方法也只會執行一次
    this.init();
  }

  init() {
    const self = this;
    // init只會執行一次,因此這兩邊界處理方法放這就行
    // 把瀏覽器localStorage裏面的剩餘數據打完
    this.dotFromLocalStorage();
    // 註冊客戶端webview的關閉生命鉤子事件
    this.beforeLeaveWebview();

    this._observer = new IntersectionObserver(function (entries, observer) {
      entries.forEach(entry => {
        // 這段邏輯,是每個商品進入視窗時都會觸發的
        if (entry.isIntersecting) {
          // 清楚當前定時器
          clearTimeout(self._timer);
          // 我這裏是直接把商品相關的數據直接放DOM上面了 好比 <div {...什麼id class style等屬性} :data-dot="渲染商品流時自行加上自身屬性" ></div>
          const ctm = entry.target.attributes['data-dot'].value;
          // 把收集到的數據添加進待上報的數據數組中
          self.dotDataArr.push(ctm);
          // 收集到該商品的數據後,取消對該商品DOM的觀察
          self._observer.unobserve(entry.target);
          // 超過必定數量打點,打完點會刪除這一批
          if (self.dotDataArr.length >= self.maxNum) {
            self.dot();
          } else {
            self.storeIntoLocalstorage(self.dotDataArr);
            if (self.dotDataArr.length > 0) {
              //,只要有新的ctm進來 接下來若是沒增長 自動2秒後打
              self._timer = window.setTimeout(function () {
                self.dot();
              }, 2000)
            }
          }
        }
      })
    }, {
        root: null,
        rootMargin: "0px",
        threshold: 0.5 // 不必定非得所有露出來 這個閾值能夠小一點點
      });

  }

  // 每一個商品都會會經過全局惟一的Exposure的實例來執行該add方法,將本身添加進觀察者中
  add(entry) {
    this._observer && this._observer.observe(entry.el)
  }

  dot() {
    // 同時刪除這批打點的ctms
    const dotDataArr = this.dotDataArr.splice(0, this.maxNum);
    DotData(dotDataArr);
    // 打完點,也順便更新一下localStorage
    this.storeIntoLocalstorage(this.dotDataArr);
  }

  storeIntoLocalstorage(dotDataArr) {
    // 。。。 存進localStorage中,具體什麼格式的字符串自行定義就好
  }

  dotFromLocalStorage() {
    const ctmsStr = window.localStorage.getItem('dotDataArr');
    if (ctmsStr) {
      // 。。。若是有數據,就上報打點
    }
  }

  beforeLeaveWebview() {
    let win: any = window;
    // 自行跟客戶端童鞋約定該鉤子的實現就好
    injectEvent("webviewWillDisappear", () => {
      if (this.dotDataArr.length > 0) {
        DotData(this.dotDataArr);
      }
    })
  }
}複製代碼

二、vue實例化+封裝指令

[自定義指令 — Vue.js]
github

// 入口JS文件 main.js
// 引入Exposure類
// exp就是那個全局惟一的實例
const exp = new Exposure();

// vue封裝一個指令,每一個使用了該指令的商品都會自動add自身進觀察者中
Vue.directive('exp-dot', {
    bind(el, binding, vnode) {
        exp.add({el: el, val: binding.value})
    }
})複製代碼

三、商品使用

循環時對每一個商品使用指令便可web

 :data-dot="item.dotData"就是咱們要收集的數據
算法

<div 
    v-exp-dot 
    v-for="item in list" 
    :key="item.id" 
    class="" 
    :data-dot="item.dotData"
>
  // ... 
</div>複製代碼

在一開始的方案裏,咱們打算每次手動觸發對一批商品的觀察,好比上拉加載生成了一批新的商品、手動交互加載了一批新的商品等,直接操做observer實例去觀察這一批新的商品 

後面發現,跟業務邏輯耦合過重,並且一個頁面的所有商品不必定是乖乖的按順序由一個數組循環渲染而成,還可能存在各類各樣的資源位,有各類各樣的出現理由,還不如直接讓每一個商品自行觸發被觀察


5、更多

感謝您耐心看到這裏,但願有所收穫!

我在學習過程當中喜歡作記錄,分享的是本身在前端之路上的一些積累和思考,但願能跟你們一塊兒交流與進步,更多文章請看【amandakelake的Github博客】


參考

[基於IntersectionObserver的曝光統計測試 | xgfe]

[Beforeunload打點丟失緣由分析及解決方案]

相關文章
相關標籤/搜索