封装了自定义Hook
interface UseExposureProps {
onExposure: (extra: Record<string, any>) => void;
idPrefix: string;
extra?: Record<string, any>;
once?: boolean;
thresholds?: number;
relativeTo?: string;
margins?: { top: number; bottom: number; left: number; right: number };
throttleTime?: number;
}
/**
* 曝光埋点Hook
*/
export function useExposure({
onExposure, // 曝光回调
idPrefix, // 唯一id前缀
extra = {}, // 额外参数,可用于回传给曝光回调
once = false, // 是否只曝光一次
thresholds = 0.8,// 元素曝光触发阈值(比如漏出80%就触发)
relativeTo = 'viewport',// 相对于视口还是指定区域
margins = { top: 0, bottom: 0, left: 0, right: 0 }, // 偏移量
throttleTime = 500, // 节流时间(可以不要其实)
}: UseExposureProps<any>) {
const domId = useRef(getUniqueId(idPrefix));
// 存储上一次曝光触发时间
const lastExposureTimeRef = useRef(0);
useEffect(() => {
// 创建IntersectionObserver
const observer = Taro.createIntersectionObserver(this, {
thresholds: [thresholds], // 可见率超过阈值触发
});
relativeTo === 'viewport'
? observer.relativeToViewport(margins)
: observer.relativeTo(relativeTo, margins);
// 监听元素可见性变化,这里使用深度选择器,最外层改为自己项目的最外层
observer.observe(`._pageWrap >>> .${domId.current}`, (res: any) => {
// 可见率超过阈值触发回调
if (res.intersectionRatio > thresholds) {
// 节流防抖逻辑:检查是否在指定时间内已触发过
const now = Date.now();
if (
throttleTime > 0 &&
now - lastExposureTimeRef.current < throttleTime
) {
return; // 在节流时间内,跳过本次触发
}
lastExposureTimeRef.current = now; // 更新最后触发时间
// 触发外部回调 (由组件处理上报)
onExposure(extra);
// 清理观察器,只触发一次
once && observer?.disconnect();
}
});
// 组件卸载时清理观察器
return () => {
observer?.disconnect();
};
}, []);
return { domId: domId.current };
}
使用时:
const { domId } = useExposure({
idPrefix: 'goods',
onExposure: () => {};
});
return(
<View className={domId}></View>
)
一些坑:
- 获取domId的dom元素,需要使用Taro的深度选择器'>>>',要不然选择不到dom的,同理使用Taro.createSelectorQuery().select(),也是要使用深度选择器就能跨组件选中dom,这是个大坑
- useEffect的依赖项不要传入依赖,要不然会多次监听,重复触发
- 节流时间可以不要,没什么实际用