實習好忙|前端埋點之曝光實現

最近有一個工做需求是曝光埋點,讓我得以有機會接觸相關的東西。以前實習時沒有作過這方面的需求,我的項目更是和埋點扯不上關係。以致於上週開會討論時聽到「埋點」這個詞就慫了。vue

不事後面聽大佬分析了下後才意識到,原來「埋點」是這個意思。曝光埋點的思路也是很簡單:無非是判斷某個DOM是否出如今視窗中,出現了就收集數據上報給服務端。git

所謂「埋點」,是數據採集領域(尤爲是用戶行爲數據採集領域)的術語,指的是針對特定用戶行爲或事件進行捕獲、處理和發送的相關技術及其實施過程。好比用戶某個文章點擊次數、觀看某個視頻的時長等等。

再說「曝光埋點」,它與「圖片懶加載」「計算廣告瀏覽量」這些需求同樣,本質就是讓你計算某一元素和另外一元素(視窗)的相對可視狀態/相對位置,而後進行一些操做(通常是上報給服務端)。github

思考如何實現

最早出如今腦海裏的方法是利用getBoundingClientRect / offset類 + onscroll。即:註冊滾動事件,而後在滾動的回調函數中利用getBoundingClientRect / offset類拿到每一個元素的位置信息,而後通過判斷肯定是否元素處於曝光狀態/視窗中。ajax

但這種方式有很大的缺陷。若是你熟悉瀏覽器的渲染過程的話,就會知道調用getBoundingClientRect / offset類會引發瀏覽器的迴流重繪,影響網頁表現/性能。頻繁、大量調用更不是一個穩當的選擇。typescript

我開始嘗試在社區找找看有沒有其餘更穩當的方法,還真被我找到了:Intersection Observersegmentfault

Intersection Observer

它提供了一種異步觀察目標元素與祖先元素或頂級文檔Viewport的交集變化的方法。也就是說,不只能夠用來得到相對於視窗的曝光,能夠作得更多,這取決於「另外一個元素」是什麼。瀏覽器

Intersection Observer將原本是開發者作的:監聽滾動、遍歷獲取元素與另外一個元素(或視窗)相對位置的工做給作了。這兩塊工做是頁面性能損耗大戶,如今交給瀏覽器來實現,會比咱們開發者來作要穩當的多。開發者如今只須要關心其餘業務邏輯便可 😁異步

那這麼好用的API,它的兼容性情況如何呢?函數

兼容性

還不錯,但兼容性方面要求高的話仍是不能讓人放心使用。性能

Polyfill

但不用擔憂,咱們有polyfill。W3C提供了一個polyfill,當瀏覽器不支持時使用常規解決方案替代。它的思路就是在檢測到當前瀏覽器不支持Intersection Observer API時,使用getBoundingClientRect去從新實現一遍Intersection Observer API

那麼使用了該Polyfill後,瀏覽器兼容性情況如何呢?

很是棒! 😎(IE6都支持了,還想啥呢,大兄弟。)

Polyfill兼容性

曝光實現步驟

思路就像上面一再提到的,很簡單:

  1. new IntersectionObserver() 實例化一個全局observer,(結合Vue指令)讓每一個DOM自行把本身加入到observer的觀察列表。
  2. 當某個DOM進入視窗,收集對應的信息,上報。
  3. 取消對該DOM的觀察。

代碼實現

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>
相關文章
相關標籤/搜索