最近有一個工做需求是曝光埋點,讓我得以有機會接觸相關的東西。以前實習時沒有作過這方面的需求,我的項目更是和埋點扯不上關係。以致於上週開會討論時聽到「埋點」這個詞就慫了。vue
不事後面聽大佬分析了下後才意識到,原來「埋點」是這個意思。曝光埋點的思路也是很簡單:無非是判斷某個DOM是否出如今視窗中,出現了就收集數據上報給服務端。git
所謂「埋點」,是數據採集領域(尤爲是用戶行爲數據採集領域)的術語,指的是針對特定用戶行爲或事件進行捕獲、處理和發送的相關技術及其實施過程。好比用戶某個文章點擊次數、觀看某個視頻的時長等等。
再說「曝光埋點」,它與「圖片懶加載」「計算廣告瀏覽量」這些需求同樣,本質就是讓你計算某一元素和另外一元素(視窗)的相對可視狀態/相對位置,而後進行一些操做(通常是上報給服務端)。github
最早出如今腦海裏的方法是利用getBoundingClientRect
/ offset類
+ onscroll
。即:註冊滾動事件,而後在滾動的回調函數中利用getBoundingClientRect
/ offset類
拿到每一個元素的位置信息,而後通過判斷肯定是否元素處於曝光狀態/視窗中。ajax
但這種方式有很大的缺陷。若是你熟悉瀏覽器的渲染過程的話,就會知道調用getBoundingClientRect
/ offset類
會引發瀏覽器的迴流重繪,影響網頁表現/性能。頻繁、大量調用更不是一個穩當的選擇。typescript
我開始嘗試在社區找找看有沒有其餘更穩當的方法,還真被我找到了:Intersection Observersegmentfault
它提供了一種異步觀察目標元素與祖先元素或頂級文檔Viewport的交集變化的方法。也就是說,不只能夠用來得到相對於視窗的曝光,能夠作得更多,這取決於「另外一個元素」是什麼。瀏覽器
Intersection Observer
將原本是開發者作的:監聽滾動、遍歷獲取元素與另外一個元素(或視窗)相對位置的工做給作了。這兩塊工做是頁面性能損耗大戶,如今交給瀏覽器來實現,會比咱們開發者來作要穩當的多。開發者如今只須要關心其餘業務邏輯便可 😁異步
那這麼好用的API,它的兼容性情況如何呢?函數
還不錯,但兼容性方面要求高的話仍是不能讓人放心使用。性能
Polyfill
但不用擔憂,咱們有polyfill。W3C提供了一個polyfill,當瀏覽器不支持時使用常規解決方案替代。它的思路就是在檢測到當前瀏覽器不支持Intersection Observer API
時,使用getBoundingClientRect
去從新實現一遍Intersection Observer API
。
那麼使用了該Polyfill後,瀏覽器兼容性情況如何呢?
很是棒! 😎(IE6都支持了,還想啥呢,大兄弟。)
思路就像上面一再提到的,很簡單:
new IntersectionObserver()
實例化一個全局observer
,(結合Vue指令)讓每一個DOM自行把本身加入到observer
的觀察列表。Exposure.ts 封裝成類
import 'intersection-observer'; export default class Exposure { private observer: IntersectionObserver | undefined; constructor() { this.init(); } private init() { const self = this; this.observer = new IntersectionObserver( (entries, observer) => { entries.forEach(item => { if (item.isIntersecting) { const data = item.target.getAttribute('data-article'); self.upload(data); observer!.unobserve(item.target); } }); }, { root: null, rootMargin: '0', threshold: 0.1, } ); } public add(el: Element) { this.observer && this.observer.observe(el); } private upload(data: string | null) { if (data) { // ajax上報數據 } } }
directive/exposure.ts 封裝Vue指令
import Exposure from '@/lib/Exposure'; import Vue from 'vue'; const exposure = new Exposure(); Vue.directive('exposure', { bind(el) { exposure.add(el); }, });
*.vue 使用指令
<div v-exposure :data-article='article'> ... </div>