小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
基于NuxtJS 2.x
构建的项目实现
- 通过监听容器
onscroll
事件,判断元素是否在可视窗口范围内,进行数据曝光 - 通过
html5
新增的data-*
属性实现数据埋点,传递曝光数据
数据埋点
- 创建
productExposure.js
文件
window.productExposure = {
// 获取目标元素相对body的偏移量
offsetAction(curEle) {
// 获取目标相对body的偏移值
let totalTop = null;
let par = curEle.offsetParent;
// 加自己本身的上偏移
totalTop += curEle.offsetTop;
// 只要没有找到body,就把父级参照物的边框和偏移也进行累加
while (par) {
// 累加父级参照物本身的偏移
totalTop += par.offsetTop;
par = par.offsetParent;
}
return totalTop;
},
// 监听滚动方法
scrollAction() {
const _this = this;
_this.targetList.forEach((element, index) => {
if (element) {
if (
element.bodyOffsetTop + element.clientHeight / 2 <=
_this.wrap.bodyOffsetTop +
_this.wrap.clientHeight +
_this.wrap.scrollTop
) {
// 当出现在可视窗口范围内时,上报数据;数据从 `dataset` 属性中获取
_this.option.callback(_this.targetList[index].dataset);
// 从监听目标中移出该节点,避免重复上报
delete _this.targetList[index];
}
}
});
},
// 格式数据
watchAction() {
const _this = this;
// 获取容器相对body的偏移值
_this.wrap.bodyOffsetTop = _this.offsetAction(_this.wrap);
_this.targetList.forEach((element, index) => {
if (element) {
// 为每项添加相对body的偏移值
element.bodyOffsetTop = _this.offsetAction(_this.domList[index]);
}
});
},
// 初始化
init(option) {
// 获取容器
this.option = option;
this.wrap = document.getElementById(this.option.wrapId);
if (this.wrap) {
// 获取监听目标
if (this.option.domClass) {
this.domList = document.querySelectorAll(this.option.domClass);
if (this.domList.length > 0) {
this.targetList = Array.prototype.slice.call(this.domList); // dom数组对象转普通数组
this.watchAction();
this.scrollAction();
// 先移除之前的滚动监听
this.wrap.removeEventListener(
"scroll",
this.scrollAction.bind(this),
false
);
// 监听容器滚动
this.wrap.addEventListener(
"scroll",
this.scrollAction.bind(this),
false
);
}
}
}
},
// 更新
update() {
if (this.wrap) {
if (this.domList && this.targetList) {
this.watchAction();
this.scrollAction();
}
}
},
};
复制代码
- 在配置文件
nuxt.config.js
中引入曝光文件,其中defer
用于延迟加载,避免阻塞 DOM 渲染
module.exports = {
mode: "universal",
head: {
// 引入文件
script: [{ src: "/js/common/productExposure.js", defer: "defer" }],
__dangerouslyDisableSanitizers: ["script"],
},
};
复制代码
批量曝光
主要实现:
- 将曝光数据信息存在 localStorage 中
- 进入页面先上传一次,以免之前还没到一分钟时间就退出系统,导致数据没有上传成功
- 之后是定时上传,1 分钟上传一次
// store/index.js
import * as axios from "axios";
import { getLocalCache, setLocalCache } from "../assets/js/utils.js";
export const state = () => {
return {
timer: 0,
};
};
export const mutations = {
start(state, timer) {
let dataArr = getLocalCache("exposureData");
if (!dataArr) {
dataArr = [];
return;
}
dataArr = JSON.parse(dataArr);
setLocalCache("exposureData", []);
const splitArrs = chunk(dataArr, 500);
splitArrs.forEach((item) => {
axios.post("/api/xxx/exposure", { para: { list: item } });
});
},
};
export const actions = {
start: ({ commit }) => {
// 定时,一分钟批量上传一次
clearInterval(state.timer);
state.timer = setInterval(() => {
commit("start", state.timer);
}, 1 * 60 * 1000);
},
continue: ({ commit }) => {
// 进入页面,先上传一次
const dataArr = getLocalCache("exposureData");
if (dataArr && dataArr.length > 0) {
commit("start");
}
},
};
// 数据分割函数
const chunk = (arr, size) => {
size = size * 1 || 1;
return Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
arr.slice(i * size, i * size + size)
);
};
复制代码
实战使用
<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"
/>
</div>
</div>
</template>
<script>
// 初始化配置,监听产品曝光信息
window.productExposure.init({
wrapId: "mescroll",
domClass: ".list-product .list-item",
callback: (value) => {
// 曝光事件
platformExposure(value);
},
});
// 曝光统计
const platformExposure = (value) => {
const date = new Date(); // 获取当前时间
const para = {
platform_id: value.id,
campaign_url: value.url,
time: date.getTime(),
};
let ctmsStr = getLocalCache("exposureData");
ctmsStr = ctmsStr ? JSON.parse(ctmsStr) : [];
ctmsStr.push(para);
setLocalCache(exposureData, JSON.stringify(ctmsStr));
};
</script>
复制代码