Post

前端埋点实现:用户行为统计

前端埋点实现:用户行为统计

/c8software/sensordata-demo

主要内容

  • Event Listeners:监听用户交互。
  • Performance API:获取页面加载时间。
  • navigator.sendBeacon:可靠发送数据。
  • JavaScript 原生 API:确保轻量和兼容性。

1:初始化项目

1
2
3
4
mkdir behavior-tracking
cd behavior-tracking
npm init -y
npm install rollup --save-dev

文件结构:

1
2
3
4
5
6
7
behavior-tracking/
├── src/
│   ├── index.js      # 主入口
│   ├── tracker.js    # 埋点逻辑
│   ├── report.js     # 数据上报
├── rollup.config.js
└── package.json

2:埋点逻辑实现

src/tracker.js 中,实现行为统计:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
export class BehaviorTracker {
  constructor() {
    this.events = [];
    this.startTime = Date.now();
    this.init();
  }

  init() {
    // 页面浏览
    this.trackPageView();

    // 点击事件
    document.addEventListener('click', (e) => {
      const target = e.target;
      if (target.tagName.match(/^(A|BUTTON|INPUT)$/)) {
        this.trackClick(target);
      }
    });

    // 页面卸载时计算停留时间
    window.addEventListener('unload', () => {
      this.trackStayTime();
    });
  }

  trackPageView() {
    this.events.push({
      type: 'pageview',
      url: window.location.href,
      title: document.title,
      timestamp: Date.now(),
      loadTime: performance.timing.loadEventEnd - performance.timing.navigationStart
    });
  }

  trackClick(target) {
    this.events.push({
      type: 'click',
      element: target.tagName,
      id: target.id || null,
      text: target.innerText || target.value || null,
      x: target.getBoundingClientRect().x,
      y: target.getBoundingClientRect().y,
      timestamp: Date.now()
    });
  }

  trackStayTime() {
    const stayTime = Date.now() - this.startTime;
    this.events.push({
      type: 'stay',
      url: window.location.href,
      duration: stayTime,
      timestamp: Date.now()
    });
  }

  getEvents() {
    return this.events;
  }
}

实现要点

  • 页面浏览:记录 URL、标题和加载时间。
  • 点击事件:针对交互元素(如 <a><button>),采集位置和内容。
  • 停留时间:通过 unload 事件计算页面停留时长。

3:数据上报

src/report.js 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export class Reporter {
  constructor(url) {
    this.url = url;
  }

  send(data) {
    const payload = JSON.stringify(data);
    if (navigator.sendBeacon) {
      navigator.sendBeacon(this.url, payload); // 页面关闭时仍可发送
    } else {
      fetch(this.url, { method: 'POST', body: payload, keepalive: true });
    }
  }
}

navigator.sendBeacon 是浏览器提供的一个 JavaScript API,用于异步发送小型数据(如埋点数据)到服务器。它通过 HTTP POST 请求在后台传输数据,即使页面正在卸载(如用户关闭标签页)也能保证发送成功。相比传统的 fetchXMLHttpRequestsendBeacon 的优势在于它不阻塞页面关闭,数据发送由浏览器接管,无需等待响应,确保了高可靠性和低性能开销,所以特别适合用户行为统计场景中需要在页面离开时上报数据。

4:整合埋点

src/index.js 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { BehaviorTracker } from './tracker.js';
import { Reporter } from './report.js';

export class TrackingSDK {
  constructor({ reportUrl }) {
    this.tracker = new BehaviorTracker();
    this.reporter = new Reporter(reportUrl);
    this.reportInterval = null;
  }

  start() {
    this.reportInterval = setInterval(() => {
      const events = this.tracker.getEvents();
      if (events.length > 0) {
        this.reporter.send({ events });
        this.tracker.events = []; // 清空已发送数据
      }
    }, 3000); // 每 3 秒上报一次
  }

  stop() {
    clearInterval(this.reportInterval);
    this.reporter.send({ events: this.tracker.getEvents() }); // 停止时发送剩余数据
  }

  track(eventName, properties) {
    // 手动埋点接口
    this.tracker.events.push({
      type: eventName,
      properties,
      timestamp: Date.now()
    });
  }
}

5:打包

rollup.config.js

1
2
3
4
5
6
7
8
export default {
  input: 'src/index.js',
  output: {
    file: 'dist/tracking-sdk.js',
    format: 'umd',
    name: 'TrackingSDK'
  }
};
1
npx rollup -c

6:使用 SDK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
  <a href="#">Link</a>
  <button>Button</button>
  <input type="text" placeholder="Input">
  <script src="/dist/tracking-sdk.js"></script>
  <script>
    const sdk = new TrackingSDK({ reportUrl: 'https://example.com/track' });
    sdk.start();

    // 手动埋点示例
    document.querySelector('button').addEventListener('click', () => {
      sdk.track('CustomClick', { category: 'test' });
    });
  </script>
</body>

数据格式示例

上报的数据示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "events": [
    {
      "type": "pageview",
      "url": "https://example.com",
      "title": "Home",
      "timestamp": 1604361600000,
      "loadTime": 1200
    },
    {
      "type": "click",
      "element": "BUTTON",
      "id": null,
      "text": "Button",
      "x": 50,
      "y": 100,
      "timestamp": 1604361600500
    },
    {
      "type": "stay",
      "url": "https://example.com",
      "duration": 5000,
      "timestamp": 1604361605000
    }
  ]
}

优化与扩展

  1. 节流优化:高频点击可用 throttle 减少事件量。
  2. 属性扩展:自动添加设备信息(如屏幕分辨率)。
  3. 异常处理:捕获 JS 错误并上报。
  4. 兼容性:为老浏览器(如 IE)添加 polyfill。

数据分析

服务器收到数据后,可以:

  • 计算页面 PV(Page View)。
  • 分析热门点击区域。
  • 评估用户平均停留时间。
This post is licensed under CC BY 4.0 by the author.