UNPKG

user-behavior-analysis

Version:

一个用于追踪和记录用户在网页上各种交互行为的 TypeScript 库

421 lines (419 loc) 15.6 kB
"use strict"; // userBehaviour.ts (function(window2) { const userBehaviour = function() { const defaults = { userInfo: true, clicks: true, mouseMovement: true, mouseMovementInterval: 1, mouseScroll: true, timeCount: true, clearAfterProcess: true, processTime: 15, windowResize: true, visibilitychange: true, keyboardActivity: true, pageNavigation: true, formInteractions: true, touchEvents: true, audioVideoInteraction: true, customEventRegistration: true, processData: function(results2) { console.log(results2); }, autoSendEvents: false, sendUrl: "" }; let userConfig = {}; const mem = { processInterval: null, mouseInterval: null, mousePosition: [], // [x坐标, y坐标, 时间戳] eventListeners: { scroll: null, click: null, mouseMovement: null, windowResize: null, visibilitychange: null, keyboardActivity: null, inputActivity: null, touchStart: null }, eventsFunctions: { /** * 滚动事件处理函数 * 记录页面滚动位置和时间戳 */ scroll: () => { const scrollData = [window2.scrollX, window2.scrollY, getTimeStamp()]; results.mouseScroll.push(scrollData); sendEventData("scroll", scrollData); }, /** * 点击事件处理函数 * 记录点击位置、DOM路径和时间戳 * @param e 鼠标事件对象 */ click: (e) => { results.clicks.clickCount++; const path = []; let node = ""; e.composedPath().forEach((el, i) => { const element = el; if (i !== e.composedPath().length - 1 && i !== e.composedPath().length - 2) { node = element.localName || ""; if (element.className && typeof element.className === "string") { element.classList.forEach((clE) => { node += "." + clE; }); } if (element.id) { node += "#" + element.id; } path.push(node); } }); const elementSummary = getElementSummary(e.target); const clickDetail = [e.clientX, e.clientY, elementSummary, getTimeStamp()]; results.clicks.clickDetails.push(clickDetail); sendEventData("click", clickDetail); }, /** * 鼠标移动事件处理函数 * 更新当前鼠标位置 * @param e 鼠标事件对象 */ mouseMovement: (e) => { mem.mousePosition = [e.clientX, e.clientY, getTimeStamp()]; }, /** * 窗口大小变化事件处理函数 * 记录新的窗口尺寸和时间戳 * @param e 事件对象 */ windowResize: (e) => { const windowSize = [window2.innerWidth, window2.innerHeight, getTimeStamp()]; results.windowSizes.push(windowSize); sendEventData("windowResize", windowSize); }, /** * 页面可见性变化事件处理函数 * 记录可见性状态变化并处理结果 * @param e 事件对象 */ visibilitychange: (e) => { const visibilityChange = [document.visibilityState, getTimeStamp()]; results.visibilitychanges.push(visibilityChange); sendEventData("visibilitychange", visibilityChange); processResults(); }, /** * 键盘活动事件处理函数 * 记录按键和时间戳 * @param e 键盘事件对象 */ keyboardActivity: (e) => { const elementSummary = getElementSummary(e.target); const keyboardActivity = [e.key, elementSummary, getTimeStamp()]; results.keyboardActivities.push(keyboardActivity); sendEventData("keyboardActivity", keyboardActivity); }, /** * 输入活动事件处理函数 * 记录输入框的内容变化和时间戳 * @param e 输入事件对象 */ inputActivity: (e) => { const target = e.target; const elementSummary = getElementSummary(e.target); const inputValue = target.value || ""; const inputActivity = [inputValue, elementSummary, getTimeStamp()]; results.keyboardActivities.push(inputActivity); sendEventData("inputActivity", inputActivity); }, /** * 页面导航事件处理函数 * 记录页面URL变化和时间戳 */ pageNavigation: () => { const navigationHistory = [location.href, getTimeStamp()]; results.navigationHistory.push(navigationHistory); sendEventData("pageNavigation", navigationHistory); }, /** * 表单交互事件处理函数 * 记录表单提交事件 * @param e 事件对象 */ formInteraction: (e) => { e.preventDefault(); const elementSummary = getElementSummary(e.target); const formInteraction = [elementSummary, getTimeStamp()]; results.formInteractions.push(formInteraction); sendEventData("formInteraction", formInteraction); }, /** * 触摸开始事件处理函数 * 记录触摸位置和时间戳 * @param e 触摸事件对象 */ touchStart: (e) => { if (e.touches && e.touches.length > 0) { const touch = e.touches[0]; const elementSummary = getElementSummary(touch.target); const touchEventData = ["touchstart", touch.clientX, touch.clientY, elementSummary, getTimeStamp()]; results.touchEvents.push(touchEventData); sendEventData("touchStart", touchEventData); } }, /** * 媒体交互事件处理函数 * 记录媒体播放事件 * @param e 事件对象 */ mediaInteraction: (e) => { const target = e.target; const mediaInteraction = [e.type, target.currentSrc || "", getTimeStamp()]; results.mediaInteractions.push(mediaInteraction); sendEventData("mediaInteraction", mediaInteraction); } } }; let results = {}; function resetResults() { results = { userInfo: { windowSize: [window2.innerWidth, window2.innerHeight], appCodeName: navigator.appCodeName || "", appName: navigator.appName || "", vendor: navigator.vendor || "", platform: navigator.platform || "", userAgent: navigator.userAgent || "" }, time: { startTime: 0, currentTime: 0, stopTime: 0 }, clicks: { clickCount: 0, clickDetails: [] }, mouseMovements: [], mouseScroll: [], keyboardActivities: [], navigationHistory: [], formInteractions: [], touchEvents: [], mediaInteractions: [], windowSizes: [], visibilitychanges: [] }; } resetResults(); function getElementSummary(element) { if (!element || !(element instanceof HTMLElement)) { return { tagName: "", id: "", className: "", name: "", value: "", textContent: "" }; } const target = element; return { tagName: target.tagName || "", id: target.id || "", className: typeof target.className === "string" ? target.className : "", name: target.getAttribute("name") || "", value: target.value === void 0 || target.value === null ? "" : String(target.value), textContent: target.textContent?.trim() || "" }; } function sendEventData(eventType, data) { if (userConfig.autoSendEvents && userConfig.sendUrl) { const payload = { type: eventType, data, timestamp: getTimeStamp(), url: location.href, userInfo: results.userInfo // 附加用户信息以便后台分析 }; try { if (navigator.sendBeacon) { console.log(`Sending event data to ${userConfig.sendUrl}`, payload); navigator.sendBeacon(userConfig.sendUrl, JSON.stringify(payload)); } } catch (error) { console.error("Failed to send event data:", error); } } } function getTimeStamp() { const now = /* @__PURE__ */ new Date(); const year = now.getFullYear(); const month = (now.getMonth() + 1).toString().padStart(2, "0"); const day = now.getDate().toString().padStart(2, "0"); const hours = now.getHours().toString().padStart(2, "0"); const minutes = now.getMinutes().toString().padStart(2, "0"); const seconds = now.getSeconds().toString().padStart(2, "0"); return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; } function config(ob) { userConfig = {}; Object.keys(defaults).forEach((key) => { const value = key in ob ? ob[key] : defaults[key]; userConfig[key] = value; }); } function start() { if (Object.keys(userConfig).length !== Object.keys(defaults).length) { console.log("no config provided. using default.."); userConfig = defaults; } if (userConfig.timeCount !== void 0 && userConfig.timeCount) { results.time.startTime = getTimeStamp(); } if (userConfig.mouseMovement) { window2.addEventListener("mousemove", mem.eventsFunctions.mouseMovement); mem.mouseInterval = window2.setInterval(() => { if (mem.mousePosition && mem.mousePosition.length) { const lastMovement = results.mouseMovements[results.mouseMovements.length - 1]; if (!results.mouseMovements.length || mem.mousePosition[0] !== lastMovement[0] && mem.mousePosition[1] !== lastMovement[1]) { const mousePosition = mem.mousePosition; results.mouseMovements.push(mousePosition); sendEventData("mouseMovement", mousePosition); } } }, defaults.mouseMovementInterval * 1e3); } if (userConfig.clicks) { window2.addEventListener("click", mem.eventsFunctions.click); } if (userConfig.mouseScroll) { window2.addEventListener("scroll", mem.eventsFunctions.scroll); } if (userConfig.windowResize !== false) { window2.addEventListener("resize", mem.eventsFunctions.windowResize); } if (userConfig.visibilitychange !== false) { window2.addEventListener("visibilitychange", mem.eventsFunctions.visibilitychange); } if (userConfig.keyboardActivity) { window2.addEventListener("keydown", mem.eventsFunctions.keyboardActivity); document.addEventListener("input", mem.eventsFunctions.inputActivity); } if (userConfig.pageNavigation) { const originalPushState = window2.history.pushState; window2.history.pushState = function pushState(data, unused, url) { const ret = originalPushState.call(this, data, unused, url); window2.dispatchEvent(new Event("pushstate")); window2.dispatchEvent(new Event("locationchange")); return ret; }; window2.addEventListener("popstate", mem.eventsFunctions.pageNavigation); window2.addEventListener("pushstate", mem.eventsFunctions.pageNavigation); window2.addEventListener("locationchange", mem.eventsFunctions.pageNavigation); } if (userConfig.formInteractions) { document.querySelectorAll("form").forEach( (form) => form.addEventListener("submit", mem.eventsFunctions.formInteraction) ); } if (userConfig.touchEvents) { window2.addEventListener("touchstart", mem.eventsFunctions.touchStart); } if (userConfig.audioVideoInteraction) { document.querySelectorAll("video, audio").forEach((media) => { media.addEventListener("play", mem.eventsFunctions.mediaInteraction); media.addEventListener("pause", mem.eventsFunctions.mediaInteraction); media.addEventListener("ended", mem.eventsFunctions.mediaInteraction); media.addEventListener("timeupdate", mem.eventsFunctions.mediaInteraction); }); } if (typeof userConfig.processTime === "number" && userConfig.processTime > 0) { mem.processInterval = window2.setInterval(() => { processResults(); }, userConfig.processTime * 1e3); } } function processResults() { userConfig.processData(result()); if (userConfig.clearAfterProcess) { resetResults(); } } function stop() { if (mem.processInterval !== null) { clearInterval(mem.processInterval); } if (mem.mouseInterval !== null) { clearInterval(mem.mouseInterval); } window2.removeEventListener("scroll", mem.eventsFunctions.scroll); window2.removeEventListener("click", mem.eventsFunctions.click); window2.removeEventListener("mousemove", mem.eventsFunctions.mouseMovement); window2.removeEventListener("resize", mem.eventsFunctions.windowResize); window2.removeEventListener("visibilitychange", mem.eventsFunctions.visibilitychange); window2.removeEventListener("keydown", mem.eventsFunctions.keyboardActivity); document.removeEventListener("input", mem.eventsFunctions.inputActivity); window2.removeEventListener("touchstart", mem.eventsFunctions.touchStart); if (userConfig.audioVideoInteraction) { document.querySelectorAll("video, audio").forEach((media) => { media.removeEventListener("play", mem.eventsFunctions.mediaInteraction); media.removeEventListener("pause", mem.eventsFunctions.mediaInteraction); media.removeEventListener("ended", mem.eventsFunctions.mediaInteraction); media.removeEventListener("timeupdate", mem.eventsFunctions.mediaInteraction); }); } results.time.stopTime = getTimeStamp(); processResults(); } function result() { if (userConfig.userInfo === false && results.userInfo !== void 0) { delete results.userInfo; } if (userConfig.timeCount !== void 0 && userConfig.timeCount) { results.time.currentTime = getTimeStamp(); } return results; } function showConfig() { if (Object.keys(userConfig).length !== Object.keys(defaults).length) { return defaults; } else { return userConfig; } } function registerCustomEvent(eventName, callback) { window2.addEventListener(eventName, callback); } return { /** 显示当前配置 */ showConfig, /** 设置配置选项 */ config, /** 开始追踪 */ start, /** 停止追踪 */ stop, /** 获取追踪结果 */ showResult: result, /** 手动处理结果 */ processResults, /** 注册自定义事件 */ registerCustomEvent }; }(); if (typeof window2 !== "undefined") { window2.userBehaviour = userBehaviour; } if (typeof module !== "undefined" && module.exports) { module.exports = userBehaviour; } })(window);