埋点上报

Reading time ~27 minutes

前端埋点上报代码片段

上报工具函数:

import 'intersection-observer';
import MD5 from 'md5';

export enum TrackingPage {
  MainPage = 'main_page',
}

export enum TrackingOperation {
  View = 'view',
  Click = 'click',
  Impression = 'impression',
}

export enum TrackingType {
  HotQuestion = 'HotQuestion',
  Shortcut = 'Shortcut',
}

export const TRACKING_SECTION = {
  [TrackingType.HotQuestion]: 'hot_question',
  [TrackingType.Shortcut]: 'bottom_question',
};

export const TRACKING_TARGET = {
  [TrackingType.HotQuestion]: 'question',
  [TrackingType.Shortcut]: 'question',
};

export function dispatchTrack(data?: {
  page?: TrackingPage;
  operation: TrackingOperation;
  section?: string;
  target?: string;
  data?: any;
}) {
  const { operation = TrackingOperation.View,
  page = TrackingPage.MainPage } = data || {};
  const info: any = {
    page_type: page,
    operation,
  };

  if (data?.section) {
    info.page_section = data.section;
  }

  if (data?.target) {
    info.target_type = data.target;
  }

  if (data?.data) {
    info.data = { ...data.data };
  }

  // 发送上报请求
}

const reportImpression = (name: TrackingType, data = '',
page = TrackingPage.MainPage) => {
  let tackingData = {};
  if (data) {
    try {
      if (typeof data === 'string') {
        tackingData = JSON.parse(data);
      }
    } catch (_) {
      tackingData = {};
    }
  }

  dispatchTrack({
    page,
    operation: TrackingOperation.Impression,
    section: TRACKING_SECTION[name] || '',
    target: TRACKING_TARGET[name] || '',
    data: { viewed_objects: [tackingData] },
  });
};

const viewTimerList = {} as any;

export const io = new IntersectionObserver(
  (entries) => {
    for (const entry of entries) {
      if (entry.target instanceof HTMLElement) {
        const {
          trackingPage = TrackingPage.MainPage,
          trackingName = '',
          trackingIndex = '',
          trackingData = '',
          trackingOnce,
        } = entry.target.dataset;
        const trackingKey = MD5(trackingName + trackingIndex
         + trackingData + trackingPage);

        if (viewTimerList[trackingKey]) {
          clearTimeout(viewTimerList[trackingKey]);
          viewTimerList[trackingKey] = null;
        }

        if (entry.intersectionRatio > 0.5) {
          viewTimerList[trackingKey] = setTimeout(() => {
            reportImpression(trackingName as TrackingType, 
            trackingData, trackingPage as TrackingPage);

            if (trackingOnce) {
              removeObserver(entry.target as HTMLElement);
            }
          }, 1001); // 超过1s上报
        }
      }
    }
  },
  { threshold: [0.5] }
);

const handleIo = (ref: HTMLElement | HTMLElement[],
func: 'observe' | 'unobserve') => {
  try {
    if (Array.isArray(ref)) {
      ref.forEach((element) => {
        const target = element;
        if (Array.isArray(target)) {
          io[func](target[0]);
        } else {
          io[func](target);
        }
      });
    } else {
      io[func](ref);
    }
  } catch (_) {
    // do nothing
  }
};

export const addObserver = (ref: HTMLElement | HTMLElement[])
 => {
  handleIo(ref, 'observe');
};

export const removeObserver = (ref: HTMLElement | HTMLElement[])
 => {
  handleIo(ref, 'unobserve');
};

使用:

const MyComponent = (props) => {
  const trackingName = TrackingType.MyComponentType;
  const trackingData = {
    field_a: 'A',
    field_b: 'B',
  };
  const myRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    addObserver(myRef.current);

    return () => {
      removeObserver(myRef.current);
    };
  }, []);

  return (
    <div
      ref={myRef}
      data-tracking-name={trackingName}
      data-tracking-data={JSON.stringify(trackingData)}
      data-tracking-once
    >
      ...
    </div>
  );
};

onblur 和 onclick 执行顺序

onblur 和 onclick 执行顺序问题和解决方法 Continue reading

批量上报

Published on November 16, 2023

页面打开成功率

Published on November 15, 2023