在電商產品中(能夠打開你的淘寶、天貓、京東App),經過對商品的曝光進行數據埋點,就能反推出用戶的行爲和交互習慣,從而優化推薦和搜索算法以及交互,最終的目的固然是爲了增長用戶購買力。 javascript
曝光:商品出如今用戶眼前,也就是瀏覽器視窗,可謂之曝光
html
最直白的兩種方法 前端
這兩種辦法都能用,可是getBoundingClientRect
這個API是會引發頁面迴流的,使用不當容易致使性能問題。 vue
基於此,瀏覽器特地爲咱們打造了一個[Intersection Observer API - Web API | MDN],把性能相關的細節都處理掉,讓開發者只關心業務邏輯便可
java
整個曝光打點方案基於[Intersection Observer API - Web API 接口參考 | MDN] node
這個API還比較新,至於兼容性問題可用[IntersectionObserver/polyfill· w3c]解決,本質上也是用getBoundingClientRect
計算位置,具體怎麼實現能夠去看看它的源碼
git
new IntersectionObserver()
實例化一個全局_observer
,每一個商品DOM自行把本身加入_observer
的觀察列表 (這裏會用Vue的指令來實現) dotArr
中,而後取消對該商品DOM的觀察 dotArr
中取數據進行打點比較簡單 dotArr
的數據量大於某個量maxNum
,不等定時器,直接所有上報dotArr
同時存一份在localStorage
中,同步更新數據(增長或者上報完後清空),若是用戶真的在N秒的間隔內,而數據又不夠最大上報量maxNum
就離開了頁面,那麼這批數據就等用戶下次再進頁面時,直接從localStorage
中取出來打掉,固然若是這個用戶不再進頁面或者清空了瀏覽器緩存,這一點點數據丟失能夠接受。 // 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.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實例去觀察這一批新的商品
後面發現,跟業務邏輯耦合過重,並且一個頁面的所有商品不必定是乖乖的按順序由一個數組循環渲染而成,還可能存在各類各樣的資源位,有各類各樣的出現理由,還不如直接讓每一個商品自行觸發被觀察
感謝您耐心看到這裏,但願有所收穫!
我在學習過程當中喜歡作記錄,分享的是本身在前端之路上的一些積累和思考,但願能跟你們一塊兒交流與進步,更多文章請看【amandakelake的Github博客】
參考