小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
基于NuxtJS 2.x
构建的项目实现
官网介绍:MDN Web Docs 之 Intersection Observer API
- 1、
new IntersectionObserver()
实例化一个全局_observer
,每个DOM
节点自行把自己加入_observer
的观察列表 (此处会用Vue
的指令来实现) - 2、当某个
DOM
节点进入视窗,收集该DOM
的信息,存进一个全局数组dotArr
中,然后取消对该DOM
的观察 - 3、从
dotArr
中取数据上传- 跑定时器,每隔 N 秒检查一次,如果
dotArr
有数据,就直接上报; - 如果 N 秒内,
dotArr
的数据量大于某个量maxNum
,不等定时器,直接全部上报
- 跑定时器,每隔 N 秒检查一次,如果
- 4、不漏以及不重复上报数据,用户离开页面前的边界数据处理
- 浏览器环境:
dotArr
同时存一份在localStorage
中,同步更新数据(增加或者上报完后清空),如果用户真的在 N 秒的间隔内,而数据又不够最大上报量maxNum
就离开了页面,那么这批数据就等用户下次再进页面时,直接从localStorage
中取出来上传。当然如果这个用户再也不进页面或者清空了浏览器缓存,这一点点数据丢失是可以接受。
- 浏览器环境:
曝光监听
- 创建
intersection-observer.js
文件
// 安装 intersection-observer 插件
npm install intersection-observer --save-dev
// 创建观察文件 intersection-observer.js
import "intersection-observer";
import axios from "axios";
// 数据上报方法,即网络请求
const platformExposure = (dotDataArr) => {
axios
.post("/api/xxx/exposure", { para: { list: dotDataArr } })
};
// 节流的时间,默认是100ms
IntersectionObserver.prototype.THROTTLE_TIMEOUT = 300;
const localStorage = window.localStorage;
export default class Exposure {
constructor(maxNum = 200) {
this.dotDataArr = []; // 进入视窗的DOM节点的数据
this.maxNum = maxNum;
this.timeout = 1 * 1000 * 60; // 间隔时间上传一次
this._timer = 0;
this._observer = null; // 观察者的集合
this.init(); // 全局只会实例化一次Exposure类
}
init() {
const self = this;
// init只会执行一次,边界处理方法,把浏览器localStorage里面的剩余数据上传
this.dotFromLocalStorage();
this._observer = new IntersectionObserver(
(entries, observer) => {
// 每一个产品进入视窗时都会触发
entries.forEach((entry) => {
if (entry.isIntersecting) {
// 清除当前定时器
clearTimeout(self._timer);
// 把相关的数据直接放DOM上面了,比如 <div :data-dot="哈哈" ></div>
// const ctm = entry.target.attributes['data-dot'].value
const dataset = entry.target.dataset;
const ctm = {
platform_id: dataset.id, // 产品id 必填
};
// 收集数据,进待上报的数据数组
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进来,接下来如果没增加,自动n秒后打点
self._timer = window.setTimeout(() => {
self.dot();
}, self.timeout);
}
}
}
});
},
{
root: null, // 指定根目录,也就是当目标元素显示在这个元素中时会触发监控回调。 默认值为null,即浏览器窗口
rootMargin: "0px", // 设定root元素的边框区域
threshold: 0.5, // number或number数组,控制target元素进入root元素中可见性超过的阙值,达到阈值会触发函数,也可以使用数据来让元素在进入时在不同的可见度返回多次值
}
);
}
// 每个DOM元素通过全局唯一的Exposure的实例来执行该add方法,将自己添加进观察者中
add(entry) {
this._observer && this._observer.observe(entry.el);
}
// 上传并更新缓存
dot() {
const dotDataArr = this.dotDataArr.splice(0, this.maxNum);
platformExposure(dotDataArr);
this.storeIntoLocalstorage(this.dotDataArr);
}
// 缓存数据
storeIntoLocalstorage(dotDataArr) {
localStorage.setItem("dotDataArr", JSON.stringify(dotDataArr));
}
// 上传数据
dotFromLocalStorage() {
const ctmsStr = JSON.parse(localStorage.getItem("dotDataArr"));
if (ctmsStr && ctmsStr.length > 0) {
platformExposure(ctmsStr);
}
}
}
曝光指令
- 完成曝光指令文件
directives.client.js
import Vue from "vue";
import Exposure from "./intersection-observer";
// exp 全局唯一的实例
const exp = new Exposure();
Vue.directive("exp-dot", {
bind(el, binding, vnode) {
// 每个使用了该指令的商品都会自动add自身进观察者中
exp.add({ el, val: binding.value });
},
update(newValue, oldValue) {
// 值更新时的工作
// 也会以初始值为参数调用一次, 此时可以根据传值类型来进行相应埋点行为的请求处理
},
unbind() {
// 清理工作
},
});
- 在配置文件
nuxt.config.js
中引入指令文件directives.client.js
,其中client
代表只在客户端生效
// nuxt.config.js
module.exports = {
mode: "universal",
plugins: [{ src: "~plugins/directives.client.js" }],
};
实战使用
核心使用就是v-exp-dot
<template>
<div class="mescroll">
<div class="list-product">
<div
class="list-item"
v-for="(item, index) in listData"
:key="item.id"
:item="item"
:data-id="item.id"
:data-url="item.url"
v-exp-dot
/>
</div>
</div>
</template>