@yqg/multiple-click
Version:
Monitor user's multiple click behavior and report
103 lines (86 loc) • 3.68 kB
text/typescript
import {MouseEventHandler, MultipleClickTrackingConfig, TriggerItem, TriggerQueue} from './type';
/** 监听器工厂函数 */
export const trackingHandlerFactory = ({
interval,
continuousCount,
range,
uploadTrackingInfo,
filter,
excludeRules,
}: MultipleClickTrackingConfig): MouseEventHandler => {
// 缓存click事件的队列
const triggerQueue: TriggerQueue = [];
const appendTriggerItem = (item: TriggerItem) => {
triggerQueue.push(item);
let delIndex = -1;
for (let i = 0, len = triggerQueue.length - 1; i < len; i++) {
const {timeStamp: t1} = triggerQueue[i];
const {timeStamp: t2} = triggerQueue[i + 1];
if (t2 - t1 > interval) {
delIndex = i;
}
}
triggerQueue.splice(0, delIndex + 1);
// 简易地判断多个点是否落在一个半径为x的圆内:任意两点的距离小于直径2x
for (let i = 0, len = triggerQueue.length; i < len; i++) {
// 每次循环前检查,如果当前队列长度小于连续点击次数,直接返回,不进行无效计算
if (triggerQueue.length < continuousCount) {
return;
}
for (let j = i + 1; j < triggerQueue.length; j++) {
const {pageX: x1, pageY: y1} = triggerQueue[i];
const {pageX: x2, pageY: y2} = triggerQueue[j];
// 两点距离小于等于直径
if (Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) <= range * 2) {
continue;
}
// 两点距离大于直径,且其中一点为的下标为i,跳出本次循环
const delItem = triggerQueue.shift();
if (delItem === triggerQueue[i]) {
break;
}
}
}
};
// 监听器
const trackingHandler: MouseEventHandler = (event: MouseEvent) => {
try {
const {target, timeStamp, pageX, pageY} = event;
if (!target || !(target instanceof Element) || target === document.body || filter(event)) {
return;
}
if (excludeRules.length) {
const pathname = new URL(location.href).pathname;
const isMatch = !!excludeRules.find(({selector, shallow, page}) => {
if (page.length && page.indexOf(pathname) === -1) {
return false;
}
return shallow ? target.matches(selector) : target.closest(selector);
});
if (isMatch) {
return;
}
}
const triggerItem: TriggerItem = {
timeStamp,
target,
pageX,
pageY,
};
appendTriggerItem(triggerItem);
if (triggerQueue.length >= continuousCount) {
// 在上报前检查,是否是由于多次点击快捷选中文本而导致的上报
const elementText = (target as HTMLDivElement)?.innerText || (target as HTMLInputElement)?.value;
const isSelectionReport = elementText && window.getSelection().toString().includes(elementText);
if (!isSelectionReport) {
uploadTrackingInfo(event);
}
triggerQueue.length = 0;
}
} catch (error) {
// eslint-disable-next-line no-console
console.error('多次点击上报监听器异常:', error);
}
};
return trackingHandler;
};