如何实现元素的曝光监测

news/2024/9/19 15:43:17/文章来源:https://www.cnblogs.com/dtux/p/18302635

我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。

本文作者:霁明

一些名词解释

曝光
页面上某一个元素、组件或模块被用户浏览了,则称这个元素、组件或模块被曝光了。
视图元素
将页面上展示的元素、组件或模块统称为视图元素。
可见比例
视图元素在可视区域面积/视图元素整体面积。
有效停留时长
视图元素由不可见到可见,满足可见比例并且保持可见状态的持续的一段时间。
重复曝光
在同一页面,某个视图元素不发生DOM卸载或页面切换的情况下,发生的多次曝光称为重复曝光。例如页面上某个视图元素,在页面来回滚动时,则会重复曝光。

如何监测曝光

需要考虑的一些问题

曝光条件
页面上某一视图元素的可见比例达到一定值(例如0.5),且有效停留时间达到一定时长(例如500ms),则称该视图元素被曝光了。
如何检测可见比例
使用 IntersectionObserver api 对元素进行监听,通过 threshold 配置项设置可见比例,当达到可见比例时,观察器的回调就会执行。
IntersectionObserver 使用示例:

let callback = (entries, observer) => {entries.forEach((entry) => {// 每个条目描述一个目标元素观测点的交叉变化://   entry.boundingClientRect//   entry.intersectionRatio//   entry.intersectionRect//   entry.isIntersecting//   entry.rootBounds//   entry.target//   entry.time});
};
let options = {threshold: 1.0,
};
let observer = new IntersectionObserver(callback, options);let target = document.querySelector("#listItem");
observer.observe(target);

如何监听动态元素
使用 IntersectionObserver 对元素进行监听之前,需要先获取到元素的 DOM,但对于一些动态渲染的元素,则无法进行监听。所以,需要先监听DOM元素是否发生挂载或卸载,然后对元素动态使用IntersectionObserver 进行监听,可以使用 MutationObserver 对 DOM变更进行监听。
MutationObserver的使用示例:

// 选择需要观察变动的节点
const targetNode = document.getElementById("some-id");// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };// 当观察到变动时执行的回调函数
const callback = function (mutationsList, observer) {for (let mutation of mutationsList) {if (mutation.type === "childList") {console.log("A child node has been added or removed.");} else if (mutation.type === "attributes") {console.log("The " + mutation.attributeName + " attribute was modified.");}}
};// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);// 以上述配置开始观察目标节点
observer.observe(targetNode, config);// 之后,可停止观察
observer.disconnect();

如何监听停留时长
维护一个观察列表,元素可见比例满足要求时,将该元素信息(包含曝光开始时间)添加到列表,当元素退出可视区域时(可见比例小于设定值),用当前时间减去曝光开始时间,则可获得停留时长。

总体实现

实现一个exposure方法,支持传入需要检测曝光的元素信息(需包含className),使用 IntersectionObserver 和 MutationObserver 对元素进行动态监听。

  • 初始化时,根据className查找出已渲染的曝光监测元素,然后使用IntersectionObserver统一监听,如果有元素发生曝光,则触发对应曝光事件;
  • 对于一些动态渲染的曝光监测元素,需要使用MutationObserver监听dom变化。当有节点新增时,新增节点若包含曝光监测元素,则使用IntersectionObserver进行监听;当有节点被移除时,移除节点若包含曝光监测元素,则取消对其的监听;
  • 维护一个observe列表,元素开始曝光时将元素信息添加到列表,元素退出曝光时如果曝光时长符合规则,则触发对应曝光事件,并在observe列表中将该元素标记为已曝光,已曝光后再重复曝光则不进行采集。如果元素在DOM上被卸载,则将该元素在observe列表中的曝光事件删除,下次重新挂载时,则重新采集。
  • 设置一个定时器,定时检查observe列表,若列表中有未完成曝光且符合曝光时长规则的元素,则触发其曝光事件,并更新列表中曝光信息。

初始化流程

file

元素发生挂载或卸载过程

file

元素曝光过程

file

代码实现

const exposure = (trackElems?: ITrackElem[]) => {const trackClassNames =trackElems?.filter((elem) => elem.eventType === TrackEventType.EXPOSURE).map((elem) => elem.className) || [];const intersectionObserver = new IntersectionObserver((entries) => {entries.forEach((entry) => {const entryElem = entry.target;const observeList = getObserveList();let expId = entryElem.getAttribute(EXPOSURE_ID_ATTR);if (expId) {// 若已经曝光过,则不进行采集const currentItem = observeList.find((o) => o.id === expId);if (currentItem.hasExposed) return;}if (entry.isIntersecting) {if (!expId) {expId = getRandomStr(8);entryElem.setAttribute(EXPOSURE_ID_ATTR, expId);}const exit = observeList.find((o) => o.id === expId);if (!exit) {// 把当前曝光事件推入observe列表const trackElem = trackElems.find((item) =>entryElem?.classList?.contains(item.className));const observeItem = { ...trackElem, id: expId, time: Date.now() };observeList.push(observeItem);setObserveList(observeList);}} else {if (!expId) return;const currentItem = observeList.find((o) => o.id === expId);if (currentItem) {if (Date.now() - currentItem.time > 500) {// 触发曝光事件,并更新observe列表中的曝光信息tracker.track(currentItem.event,TrackEventType.EXPOSURE,currentItem.params);currentItem.hasExposed = true;setObserveList(observeList);}}}});},{ threshold: 0.5 });const observeElems = (queryDom: Element | Document) => {trackClassNames.forEach((name) => {const elem = queryDom.getElementsByClassName?.(name)?.[0];if (elem) {intersectionObserver.observe(elem);}});};const mutationObserver = new MutationObserver((mutationList) => {mutationList.forEach((mutation) => {if (mutation.type !== 'childList') return;mutation.addedNodes.forEach((node: Element) => {observeElems(node);});mutation.removedNodes.forEach((node: Element) => {trackClassNames.forEach((item) => {const elem = node.getElementsByClassName?.(item)?.[0];if (!elem) return;const expId = elem.getAttribute('data-exposure-id');if (expId) {const observeList = getObserveList();const index = observeList.findIndex((o) => o.id === expId);if (index > -1) {// 元素被卸载时,将其曝光事件从列表删除observeList.splice(index, 1);setObserveList(observeList);}}intersectionObserver.unobserve(elem);});});});});observeElems(document);mutationObserver.observe(document.body, {subtree: true,childList: true,});const timer = setInterval(() => {// 检查observe队列,若队列中有符合曝光时长规则的元素,则修改曝光状态,并触发曝光事件。const observeList = getObserveList();let shouldUpdate = false;observeList.forEach((o) => {if (!o.hasExposed && Date.now() - o.time > 500) {tracker.track(o.event, TrackEventType.EXPOSURE, o.params);o.hasExposed = true;shouldUpdate = true;}});if (shouldUpdate) {setObserveList(observeList);}}, 3000);return () => {mutationObserver.disconnect();intersectionObserver.disconnect();clearInterval(timer);removeObserveList();};
};export default exposure;

最后

欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star

  • 大数据分布式任务调度系统——Taier
  • 轻量级的 Web IDE UI 框架——Molecule
  • 针对大数据领域的 SQL Parser 项目——dt-sql-parser
  • 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
  • 一个速度更快、配置更灵活、使用更简单的模块打包器——ko
  • 一个针对 antd 的组件测试工具库——ant-design-testing

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/743989.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL - [17] Oracle、SQLServer、MySQL数据类型对比

题记部分 — 业精于勤荒于嬉,行成于思毁于随 —

华为云DTSE助力虎彩鲜檬优化运维效率,助力迁移上云

本文主要介绍了鲜檬摄影在数字化变革中,与华为云紧密合作,借助其DTSE专家团队,成功完成了业务系统改造、技术方案优化及迁移上云。本文分享自华为云社区《虎彩鲜檬数字化产业变革,迁移上云,助力其打造智慧婚拍新纪元》,作者: HuaweiCloudDeveloper。 来源:《华为云DTSE…

【CTF入门】BUUCTF Misc刷题(持续更新)

【CTF入门】BUUCTF Misc刷题 签到 点开发现签到题直接把flag交出来了,直接复制提交即可考点:了解CTF中flag的格式一般为flag{}

Linux 中 sed命令在指定行前添加空行

001、[root@PC1 test]# ls a.txt [root@PC1 test]# cat a.txt 33 aa 44 aa 88 aa 33 aa ff kk 33 aa 88 22 mm 88 33 44 22 99 [root@PC1 test]# sed 2 s/.*/\n&/ a.txt ## 在第二行前添加空行 33 aa 44 aa 88aa 33 aa ff kk 33 aa 88 22 mm 88 33 44 22 99 002、sed[roo…

nuxtjs2.x项目PC移动互相跳转

1、在plugins目录下新建terminalToggle.js,写入以下代码 (function () {let sUserAgent = navigator.userAgent.toLowerCase();let isIpad = sUserAgent.match(/ipad/i) == "ipad";let isIphoneOs = sUserAgent.match(/iphone os/i) == "iphone os";let i…

小车轮子测试(I2C)

I2C以及小车轮子测试I2C总线是PHILIPS公司推出的一种在电子通信控制领域常用的串行通信总线,是一种简单、双向 通信、二线制、同步的串行总线,具有连接线少、控制简单、通信速率高等优点。 端口检测 cd /dev lsI2C读写操作 使用i2cdetect命令检测i2c总线 i2cdetect -l如果红色…

Simple WPF: WPF使用Windows API发送Toast通知

以前看到Windows 10的气泡通知觉得很有意思,但是一直不知道该如何实现。最近一次上网冲浪过程中偶然的机会看到了相关资料就自己来试试。本文介绍了在WPF框架下发送Win10 Toast通知的方法。以前看到Windows 10的气泡通知觉得很有意思,但是一直不知道该如何实现。最近一次上网…

卡通AI图片生成器

AI卡通生成器是一个AI工具,你可以毫不费力地用文本创建美丽的卡通图像! AI卡通生成器简介:创意与轻松相遇的地方!  有了我们创新的人工智能工具,用文字制作迷人的卡通图像就像点击一样简单。无论你是将可爱的人工智能动物带到生活中,还是用人工智能故事漫画讲述引人入胜…

[HGAME 2023 week3]kunmusic wp

今天写了一道Hgame的题,挺有意思的,写个blog记录一下 下载附件得到三个文件,先用dnspy打开dll文件,找到main函数,发现为对资源中data的加密。因此将data直接dump下来,对其进行解密,并将解密后的文件保存为111,脚本如下: file = open(rC:\Users\usr\Desktop\ctf题库\re…

Python中 `__pycache__` 文件夹是什么?

引言 当你编写一个独立的Python脚本时,目录结构看起来可能没什么特别。但随着项目逐渐变得复杂,你引言 当你编写一个独立的Python脚本时,目录结构看起来可能没什么特别。但随着项目逐渐变得复杂,你可能会倾向于将一些功能分离到其他模块或包中。这时,你可能会发现在源文件…

WPF 滚动轮播文字(走马灯效果)

使用 Control 实现自定义控件,源码可加微信号:microsoft-zh

0185-在 VSCode 中远程调试

环境Time 2022-11-13 WSL-Ubuntu 22.04 QEMU 6.2.0 Rust 1.67.0-nightly VSCode 1.73.1前言 说明 参考:https://os.phil-opp.com/minimal-rust-kernel 目标 将上一节编写的可执行文件制作成 QEMU 可以启动的镜像。使用 QEMU 启动后,使用 VSCode 远程调试。 启动 QEMU 脚本 #!…