hc-web-log-mon
Version:
基于 JS 跨平台插件,为前端项目提供【 行为、性能、异常、请求、资源、路由、曝光、录屏 】监控手段
158 lines (145 loc) • 4.72 kB
text/typescript
import type { ElementOrList, TargetGather, AnyObj } from '../types'
import { unKnowToArray, getTimestamp, getLocationHref } from '../utils'
import { sendData } from './sendData'
import { _support } from '../utils/global'
import { SEDNEVENTTYPES } from '../common'
interface IoMap {
[key: number]: IntersectionObserver
}
interface TargetMap {
target: Element
threshold: number
observeTime: number // sdk开始监视的时间
showTime?: number // sdk检测到的开始时间
showEndTime?: number // sdk检测到的结束时间
params?: AnyObj
}
/**
* 元素曝光收集
* 收集参数:曝光开始时间、曝光结束时间、被曝光元素上附带的额外参数
* 收集契机:划出目标元素的收集范围
*/
class Intersection {
private ioMap: IoMap = {}
private targetMap: TargetMap[] = []
private options = {
root: null,
rootMargin: '0px',
threshold: 0.5 // 阀值设为0.5,当只有比例达到一半时才触发回调函数
}
/**
* 针对 threshold 生成不同监听对象 (不允许同一个dom被两个监听对象监听)
* @param threshold 阈值
*/
private initObserver(threshold: number) {
return new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const targetObj = this.targetMap.find(
mapTarget => mapTarget.target === entry.target
)
if (targetObj) {
targetObj.showTime = getTimestamp()
}
} else {
const targetObj = this.targetMap.find(
mapTarget => mapTarget.target === entry.target
)
if (targetObj) {
// 在进入页面时指定了没有在屏幕可视界面的dom,会立即触发这里
// 此时需要根据有无 showTime 区分是否为一个完整事件再去发送
if (!targetObj.showTime) return
targetObj.showEndTime = getTimestamp()
this.sendEvent(targetObj)
}
}
})
},
{
...this.options,
threshold
}
)
}
/**
* 发送事件
*/
private sendEvent(targetObj: TargetMap) {
sendData.emit({
eventType: SEDNEVENTTYPES.INTERSECTION,
triggerPageUrl: getLocationHref(),
...targetObj
})
}
/**
* 开始观察目标元素
* 分为初始加载和过程中加载
* @param params 附带的额外参数
*/
public observe(gather: TargetGather | TargetGather[]) {
const _gather = unKnowToArray(gather)
_gather.forEach(item => {
const _targetList = unKnowToArray(item.target)
if (!Object.prototype.hasOwnProperty.call(this.ioMap, item.threshold)) {
this.ioMap[item.threshold] = this.initObserver(item.threshold || 0.5)
}
_targetList.forEach(target => {
const index = this.targetMap.findIndex(
mapTarget => mapTarget.target === target
)
// 不允许重复观察
if (index === -1) {
this.ioMap[item.threshold].observe(target)
// 记录哪些元素被监听
this.targetMap.push({
target,
threshold: item.threshold,
observeTime: getTimestamp(), // 开始监听的时间
params: item.params
})
}
})
})
}
/**
* 对元素停止观察
*/
public unobserve(target: ElementOrList) {
const _targetList = unKnowToArray(target)
_targetList.forEach(_target => {
// 第一步:找出此元素代表的 threshold 值
const targetIndex = this.targetMap.findIndex(
mapTarget => mapTarget.target === _target
)
if (targetIndex === -1) return // 不存在的元素则跳过
// 第二步:根据 threshold 值从 ioMap 获取到 io 实例
const io = this.ioMap[this.targetMap[targetIndex].threshold]
if (!io) return
this.targetMap.splice(targetIndex, 1)
// 第二步:io 实例执行 unobserve 方法
io.unobserve(_target)
})
}
/**
* 对所有元素停止观察
*/
public disconnect() {
for (const key in this.ioMap) {
if (Object.prototype.hasOwnProperty.call(this.ioMap, key)) {
const io = this.ioMap[key]
io.disconnect()
}
}
this.targetMap = []
this.ioMap = {}
}
}
export let intersection: Intersection
/**
* 初始化曝光监听
*/
export function initIntersection() {
_support.intersection = new Intersection()
intersection = _support.intersection
}