前端项目数据埋点接入神策

介绍

应用中特定的流程收集一些信息,用来跟踪应用使用的状况,以便后续进一步优化产品或者提供运营的数据支撑。如访问数,访客,停留时常,页面浏览数等

数据收集类型

  1. 事件数据收集 页面访问量收集 使用监听url变化, 如果是history,则可以监听pushstate 和replacestate
    hashchange 页面停留时间收集
    页面停留时间,就是记录从访问页面到离开/关闭页面的时间。不管是SPA应用还是非SPA应用,都是记录从页面加载到页面关闭或跳转的时间,
    load => 单页面应用 replaceState, pushState, 非单页面应用popstate

    代码如下:

    // 首次进入页面 window.addEventListener("load", () => {   // 记录时间   const
    time = new Date().getTime();   dulation.startTime = time; });
    
    // 单页应用页面跳转(触发 replaceState) window.addEventListener("replaceState",
    () => {   const time = new Date().getTime();   dulation.value = time
    - dulation.startTime;
    
      // 上报   // .....
    
      // 上报后重新初始化 dulation, 以便后续跳转计算   dulation.startTime = time;  
    dulation.value = 0; });
    
    // 单页应用页面跳转(触发 pushState) window.addEventListener("pushState", () =>
    {   const time = new Date().getTime();   dulation.value = time -
    dulation.startTime;
    
      // 上报   // .....
    
      // 上报后重新初始化 dulation, 以便后续跳转计算   dulation.startTime = time;  
    dulation.value = 0; });
    
    // 非单页应用跳转触发 popstate window.addEventListener("popstate", () => {  
    const time = new Date().getTime();   dulation.value = time -
    dulation.startTime;
    
      // 上报   // .....
    
      // 上报后重新初始化 dulation, 以便后续跳转计算   dulation.startTime = time;  
    dulation.value = 0; });
    
    // 页面没有任何跳转, 直接关闭页面的情况 window.addEventListener("beforeunload", () =>
    {   const time = new Date().getTime();   dulation.value = time -
    dulation.startTime;
    
      // 上报   // .....
    
      // 上报后重新初始化 dulation, 以便后续跳转计算   dulation.startTime = time;  
    dulation.value = 0; });```
    
    ## 异常数据收集
    
    ```window.addEventListener("error", (e) => {   console.log("上报",
    "error"); });
    
    window.addEventListener("unhandledrejection", (e) => {  
    console.log("上报", "unhandledrejection"); }); ```
    

数据上报

  1. 数据格式
{
  uid; // 用户id
  title; // 页面标题
  url; // 页面的url
  time; // 触发埋点的时间
  ua; // userAgent
  screen; // 屏幕信息, 如 1920x1080
  type // 数据类型,根据触发的不同埋点有不同的类型
  data; // 根据不同的type,此处的数据也不同,如 事件数据有元素名称,事件名称、页面停留时间有停留时长....
  sdk // sdk 相关信息
}
  1. 上报方式
    一般情下使用
    XMLHttpReuqest(axios)或者Fetch,但用其有两个缺陷,一是有跨域问题,二是在页面关闭时会中断正在发送的数据。
    考虑到以上几点,一般使用图片来发送数据或者使用navigator.sendBeacon,这里就使用sendBeacon,简单方便。也可以两者结合(如果浏览器不支持sendBeancon,就使用图片的方式)。
function send(data) {
  const url = `xxxx`
  if (navigator.sendBeacon) {
    navigator.sendBeacon(url, JSON.stringify(data));
  } else {
    const imgReq = new Image();
    imgReq.src = `${url}?params=${JSON.stringify(
      data
    )}&t=${new Date().getTime()}`;
  }
}

sdk实现

// JS 完整代码部分
(function (e) {
  function wrap(event) {
    const fun = history[event];
    return function () {
      const res = fun.apply(this, arguments);

      const e = new Event(event);

      window.dispatchEvent(e);

      return res;
    };
  }

  class TrackingDemo {
    constructor(options = {}) {
      // 重写 pushState、replaceState
      window.history.pushState = wrap("pushState");
      window.history.replaceState = wrap("replaceState");

      // 上报地址
      this.reportUrl = options.reportUrl || "";
      this.sdkVersion = "1.0.0";

      this._eventList = ["click", "dblclick", "mouseout", "mouseover"];

      this._dulation = {
        startTime: 0,
        value: 0,
      };
      this._initJSError();

      // 初始化事件数据收集
      this._initEventHandler();

      // 初始化PV统计
      this._initPV();

      this._initPageDulation();
    }

    setUserId(uid) {
      this.uid = uid;
    }

    _initEventHandler() {
      this._eventList.forEach((event) => {
        window.addEventListener(event, (e) => {
          const target = e.target;
          const reportKey = target.getAttribute("report-key");
          if (reportKey) {
            this._report("event", {
              tagName: e.target.nodeName,
              tagText: e.target.innerText,
              event,
            });
          }
        });
      });
    }

    _initPV() {
      window.addEventListener("pushState", (e) => {
        this._report("pv", {
          type: "pushState",
          referrer: document.referrer,
        });
      });

      window.addEventListener("replaceState", (e) => {
        this._report("pv", {
          type: "replaceState",
          referrer: document.referrer,
        });
      });

      window.addEventListener("hashchange", () => {
        this._report("pv", {
          type: "hashchange",
          referrer: document.referrer,
        });
      });
    }

    _initPageDulation() {
      let self = this;

      function initDulation() {
        const time = new Date().getTime();
        self._dulation.value = time - self._dulation.startTime;

        self._report("dulation", {
          ...self._dulation,
        });

        self._dulation.startTime = time;
        self._dulation.value = 0;
      }

      // 首次进入页面
      window.addEventListener("load", () => {
        // 记录时间
        const time = new Date().getTime();
        this._dulation.startTime = time;
      });

      // 单页应用页面跳转(触发 replaceState)
      window.addEventListener("replaceState", () => {
        initDulation();
      });

      // 单页应用页面跳转(触发 pushState)
      window.addEventListener("pushState", () => {
        initDulation();
      });

      // 非单页应用跳转触发 popstate
      window.addEventListener("popstate", () => {
        initDulation();
      });

      // 页面没有任何跳转, 直接关闭页面的情况
      window.addEventListener("beforeunload", () => {
        initDulation();
      });
    }

    _initJSError() {
      window.addEventListener("error", (e) => {
        this._report("error", {
          message: e.message,
        });
      });

      window.addEventListener("unhandledrejection", (e) => {
        this._report("error", {
          message: e.reason,
        });
      });
    }

    // 用户可主动上报
    reportTracker(data) {
      this._report("custom", data);
    }

    _getPageInfo() {
      const { width, height } = window.screen;
      const { userAgent } = navigator;
      return {
        uid: this.uid,
        title: document.title,
        url: window.location.href,
        time: new Date().getTime(),
        ua: userAgent,
        screen: `${width}x${height}`,
      };
    }

    _report(type, data) {
      const reportData = {
        ...this._getPageInfo(),
        type,
        data,
        sdk: this.sdkVersion,
      };

      if (navigator.sendBeacon) {
        navigator.sendBeacon(this.reportUrl, JSON.stringify(reportData));
      } else {
        const imgReq = new Image();
        imgReq.src = `${this.reportUrl}?params=${JSON.stringify(
          reportData
        )}&t=${new Date().getTime()}`;
      }
    }
  }
  e.TrackingDemo = TrackingDemo;
})(window);
// HTML 代码部分
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button report-key="button">按钮</button>
</body>
<script src="./tackerDemo.js"></script>
<script>
    const trackingDemo = new TrackingDemo()
</script>
</html>

工程化

参考文档