1x1 透明 GIF 埋点请求

为什么数据埋点常用 1x1 像素透明 GIF 图片

问题

为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 GIF 图片?

解答

1x1 GIF 埋点的优势

使用 1x1 透明 GIF 做埋点是一种经典方案,主要有以下原因:

体积极小

// 1x1 透明 GIF 的 Base64 编码,仅 43 字节
R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7

相比 PNG(67 字节)和 BMP(数百字节),GIF 是最小的。

天然跨域

// img 标签不受同源策略限制
const img = new Image();
img.src = 'https://analytics.example.com/collect?event=click&id=123';

不需要服务端配置 CORS,任何域名都能发送。

不阻塞页面

// 图片加载是异步的,不会阻塞页面渲染和 JS 执行
function track(data) {
  const img = new Image();
  const params = new URLSearchParams(data).toString();
  img.src = `https://analytics.example.com/collect?${params}`;
  // 无需等待响应,发出去就行
}

track({ event: 'pageview', page: '/home' });

页面卸载时更可靠

// 页面关闭时,img 请求比 XHR 更可能发送成功
window.addEventListener('beforeunload', () => {
  // XHR 可能被取消
  // img 请求通常能发出去
  new Image().src = 'https://analytics.example.com/collect?event=leave';
});

无需处理响应

// 服务端返回 1x1 GIF,客户端不需要任何处理
// 不像 XHR 需要处理 readyState、status 等
function sendBeacon(url, data) {
  const img = new Image();
  img.src = `${url}?${new URLSearchParams(data)}`;
  // 完事,不用管了
}

完整的埋点实现

class Tracker {
  constructor(endpoint) {
    this.endpoint = endpoint;
    this.queue = [];
  }

  // 发送单个埋点
  send(data) {
    const img = new Image();
    const params = new URLSearchParams({
      ...data,
      t: Date.now(), // 防缓存
    }).toString();
    
    img.src = `${this.endpoint}?${params}`;
  }

  // 页面卸载时发送(现代方案)
  sendOnUnload(data) {
    const params = new URLSearchParams(data).toString();
    const url = `${this.endpoint}?${params}`;
    
    // 优先使用 sendBeacon(更可靠)
    if (navigator.sendBeacon) {
      navigator.sendBeacon(url);
    } else {
      // 降级到 img
      new Image().src = url;
    }
  }
}

// 使用
const tracker = new Tracker('https://analytics.example.com/collect');
tracker.send({ event: 'click', button: 'submit' });

现代替代方案

// 1. Navigator.sendBeacon - 专为埋点设计
navigator.sendBeacon('/collect', JSON.stringify({ event: 'click' }));

// 2. fetch + keepalive - 页面卸载时也能发送
fetch('/collect', {
  method: 'POST',
  body: JSON.stringify({ event: 'click' }),
  keepalive: true, // 关键:允许请求在页面卸载后继续
});

关键点

  • 体积最小:1x1 GIF 仅 43 字节,比 PNG、BMP 都小
  • 跨域无障碍:img 标签不受同源策略限制
  • 不阻塞渲染:异步加载,不影响页面性能
  • 卸载时可靠:比 XHR 更容易在页面关闭前发出
  • 实现简单:不需要处理响应,new Image() 一行搞定
  • 现代替代sendBeaconfetch + keepalive 是更好的选择