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