以前我没得选,现在我只想做个坏人

实习好忙|前端埋点之曝光实现

    大前端     前端·埋点·曝光·Intersection Observer

  1. 思考如何实现
  2. Intersection Observer
  3. 曝光实现步骤
  4. 代码实现

最近有一个工作需求是曝光埋点,让我得以有机会接触相关的东西。之前实习时没有做过这方面的需求,个人项目更是和埋点扯不上关系。以至于上周开会讨论时听到“埋点”这个词就怂了。

不过后面听大佬分析了下后才意识到,原来“埋点”是这个意思。曝光埋点的思路也是很简单:无非是判断某个DOM是否出现在视窗中,出现了就收集数据上报给服务端。

所谓“埋点”,是数据采集领域(尤其是用户行为数据采集领域)的术语,指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个文章点击次数、观看某个视频的时长等等。

再说「曝光埋点」,它与「图片懒加载」「计算广告浏览量」这些需求一样,本质就是让你计算某一元素和另一元素(视窗)的相对可视状态/相对位置,然后进行一些操作(一般是上报给服务端)。

思考如何实现

最先出现在脑海里的方法是利用getBoundingClientRect / offset类 + onscroll。即:注册滚动事件,然后在滚动的回调函数中利用getBoundingClientRect / offset类拿到每个元素的位置信息,然后经过判断确定是否元素处于曝光状态/视窗中。

但这种方式有很大的缺陷。如果你熟悉浏览器的渲染过程的话,就会知道调用getBoundingClientRect / offset类会引起浏览器的回流重绘,影响网页表现/性能。频繁、大量调用更不是一个妥当的选择。

我开始尝试在社区找找看有没有其他更妥当的方法,还真被我找到了:Intersection Observer

Intersection Observer

它提供了一种异步观察目标元素与祖先元素或顶级文档Viewport的交集变化的方法。也就是说,不仅可以用来获得相对于视窗的曝光,可以做得更多,这取决于“另一个元素”是什么。

Intersection Observer将本来是开发者做的:监听滚动、遍历获取元素与另一个元素(或视窗)相对位置的工作给做了。这两块工作是页面性能损耗大户,现在交给浏览器来实现,会比我们开发者来做要妥当的多。开发者现在只需要关心其他业务逻辑即可 😁

那这么好用的API,它的兼容性状况如何呢?

不见图请翻墙

还不错,但兼容性方面要求高的话还是不能让人放心使用。

Polyfill

但不用担心,我们有polyfill。W3C提供了一个polyfill,当浏览器不支持时使用常规解决方案替代。它的思路就是在检测到当前浏览器不支持Intersection Observer API时,使用getBoundingClientRect去重新实现一遍Intersection Observer API

那么使用了该Polyfill后,浏览器兼容性状况如何呢?

非常棒! 😎(IE7都支持了,还想啥呢,大兄弟。)

不见图请翻墙

曝光实现步骤

思路就像上面一再提到的,很简单:

  1. new IntersectionObserver()实例化一个全局observer,(结合Vue指令)让每个DOM自行把自己加入到observer的观察列表。
  2. 当某个DOM进入视窗,收集对应的信息,上报。
  3. 取消对该DOM的观察。

代码实现

Exposure.ts 封装成类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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指令

1
2
3
4
5
6
7
8
9
10
import Exposure from '@/lib/Exposure';
import Vue from 'vue';

const exposure = new Exposure();

Vue.directive('exposure', {
bind(el) {
exposure.add(el);
},
});

*.vue 使用指令

1
2
3
<div v-exposure :data-article='article'>
...
</div>

本文作者: 林水溶

Github https://github.com/shuiRong

本文链接: https://linshuirong.cn/blog/2019/12/26/前端埋点之曝光实现/

版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

page PV:  ・  site PV:  ・  site UV: