前端数据埋点,批量曝光实现

879 阅读2分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金

基于NuxtJS 2.x构建的项目实现

  1. 通过监听容器 onscroll 事件,判断元素是否在可视窗口范围内,进行数据曝光
  2. 通过 html5 新增的 data-* 属性实现数据埋点,传递曝光数据

数据埋点

  1. 创建 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();
      }
    }
  },
};
  1. 在配置文件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>