可视化埋点平台元素曝光采集intersectionObserver思路实践

目录
  • 正文
  • 设计方案
    • 测验结论
    • 完整的伪代码

正文

最近一直在开发可视化埋点系统,其中元素的曝光埋点,就是借助了 intersectionObserver 这个原生 api。也是网上推荐度比较高的方案,同时 2022 年,该 api 兼容性也已经很高,同时也有 polyfill,基本上使用无虞。

intersectionObserver 本身 api 非常简单,但是在实际使用的过程中,由于可视化埋点的一些特殊性以及对埋点准确性的要求,还是遇到了一些 dom 变更后的边缘场景,本文便是对这些边缘场景的一个记录及实现背后的一些考虑。

通常来讲,元素的埋点是开发者主动在元素中直接埋点,而笔者正在开发的可视化埋点,在数据收集侧的工作,是异步从后台获取用户需要的元素 xpath,然后再通过 xpath 寻找到元素,调用 intersectionObserver 的 observe 方法进行监听元素的曝光。所以在进行监听的时机上,都是在元素挂载到 dom 后进行元素的监听,那么初次监听,是否会触发其回调,便关系到曝光埋点的准确性。

同时,可视化埋点也监听页面 dom 的变更,当变更后,需要解除旧元素的监听,同时增加对新元素的监听。那么如果多次监听同一元素,是否会多次触发回调,也影响到曝光的准确性。

设计方案

为了快速上线第一版,笔者最初的设计方案为:

  • 根据服务端返回的 xpath,寻找到对应的 dom 元素,对元素进行 observe
  • 监听 dom 变更事件,当 dom 发生改变后,重新根据 xpath 寻找 dom 元素,对元素进行再次 observe

在该方案中,存在几个触发时机可能导致的问题:

  • 当监听发生在元素渲染到页面后,首次监听的同时,是否会触发回调(影响到初次曝光的准确性)
  • 多次监听同一 dom 元素,是否会多次触发回调(影响到 dom 变更,多次监听同一元素后,曝光的准确性)

测验结论

  • 当初次监听元素时,会立即触发一次回调
  • 多次监听同一元素,并不会多次触发回调

在上述逻辑成立的情况下,笔者最初的方案其实是可以正常工作,对于初次曝光,虽然发生在元素渲染到 dom 之后,但是由于会立即触发一次,故初次曝光能够正常上报。而当 dom 发生变更后,消失的元素,虽然没有调用 unobserve,但是由于该元素消失了,并不会影响后续曝光埋点的上报,所以并没有带来大的问题,而 dom 变更后,元素如果依然存在,虽然再次进行了监听,但是多次监听并不影响同一元素,所以其实也没有问题。

对于第一版,上线后也确实能够正常工作,但是对于没有 unobserve 这一点,由于 js 的垃圾回收机制,必须是没有引用后才会销毁,而没有 unobserve,那么内部必然会维护一份监听的元素的列表,保留了已经从 dom 中移除的元素的引用,从而造成内存泄漏。

故需要做一些策略来避免该问题(不然代码也会被吐槽),思路如下:

维护一份 xpath -> 元素的映射,当 dom 发生变更时,遍历所有 xpath 寻找对应的元素,

如果元素同映射中一致,那么表示该元素没有发生变更,此时可以直接忽略,什么都不做。

而如果元素发生变化,那么调用 unobserve 取消旧元素的监听,同时对新元素进行监听即可。

完整的伪代码

// 工具函数命名很清晰,含义不赘述
import { getEleByXpath, getXpathByEle, debounce } from 'utils';
class track {
    constructor() {
        /*
        {
            xpath: '',
            id: ''id
        }
        */
        this.config = {} // 存储远程服务端返回的埋点 xpath 信息
        /*
            [xpath]: el,
        */
        this.map = {} // 维护的 xpath -> el 映射
        this.observer = null; // intersectionObserver 实例
    }
    // 远端获取需要曝光点的元素
    getConfig() {
        return fetch('xxx').then(res => {
            this.config = res;
            this.config.forEach(item => {
                // 初始化 xpath -> el 映射
                this.map[item.xpath] = null;
            })
        });
    }
    // 监听 dom 变更
    addDomtreeMutatorObserver() {
        // 不关心属性变化
        const config = { attributes: false, childList: true, subtree: true };
        // 监听dom变更,消个抖
        const observer = new MutationObserver(debounce(this.observe.bind(this), 200));
        observer.observe(document.body, config);
    }
    // 监听元素曝光
    observe() {
        // 此处可以加个 requestIdleCallback 来增强性能
        this.config.forEach((item) => {
            const targetEl = getEleByXpath(item.xpath);
            // 新旧元素不一致才需要取消旧元素监听,增加新元素监听
            if (targetEl !== this.map[item.xpath]) {
                // 元素存在,就监听
                if (targetEl) {
                    this.observer.observe(targetEl);
                }
                // 取消旧元素的监听
                if (this.map[shadow.xpath]) {
                    this.observer.unobserve(this.map[shadow.xpath]);
                }
                // 更新map中的el
                this.map[shadow.xpath] = targetEl;
            }
            // 一致,则什么都不做
        });
    }
    // 创建 intersectionObserver
    initObserver() {
        const callback = (entries) => {
            entries.forEach((entry) => {
                if (entry.intersectionRatio > 0.2) {
                    const targetXpath = getXpath(entry.target);
                    for (let i = 0; i < this.config.length; i++) {
                        if (this.config[i].xpath === targetXpath) {
                            // xpath一致
                            // 发送曝光埋点
                        }
                    }
                }
            });
        };
        const observer = new IntersectionObserver(callback, {
            threshold: 0.2,
        });
        this.observer = observer;
    }
    init() {
        this.getConfig().then(() => {
            // 初始化 intersectionObserver
            this.initObserver();
            // 监听元素
            this.observe();
            // 监听 dom 元素变更
            this.addDomtreeMutatorObserver();
        });
    }
}

以上便是精简后的伪代码,其核心的优化点便是维护 xpath -> el 的 map(说实话,一开始笔者其实就想到了 map,但是当时想的是拿 dom 引用作为 key,拿对象做 key 显然是行不通的,就短暂放弃,先上线再说。后来想到用 xpath 做key,才有了这篇文章)。

其实本文的初衷是记录一下 intersectionObserver 的一些边缘情况及结论(其实还对 intersectionObserver 做了其他一些比较傻的测试),但是在文章编写的过程中发现干巴巴的分析,显得实在是无趣,故夹杂了实际的可视化埋点曝光采集的业务场景,希望没有写的很乱。

同时,当笔者彻底理清思路后,发现这优化其实不值一提,只是笔者最初在设计可视化埋点方案时,由于对这些 api 的不熟悉,导致的一些迷迷糊糊的摸爬滚打经验,还望懂的同学别笑。

以上就是可视化埋点平台元素曝光采集intersectionObserver思路实践的详细内容,更多关于intersectionObserver元素曝光采集的资料请关注我们其它相关文章!

(0)

相关推荐

  • 谈谈IntersectionObserver懒加载的具体使用

    概念 IntersectionObserver接口(从属于Intersection Observer API)为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段.祖先元素与视窗(viewport)被称为根(root). 这是MDN上给的官方概念,不用去管它,我粘出来只是为了显得专业点嘛... 重点看这里 监听目标元素与其祖先或视窗交叉状态的手段 ,其实就是观察一个元素是否在视窗可见. 可以看到,交叉了就是说明当前元素在视窗里,当前就是可见的了. API var

  • IntersectionObserver实现图片懒加载的示例

    API: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API 直接上源码: <!DOCTYPE html> <html> <header> <style> .list-item{ height: 400px; margin: 5px; background-color: lightblue; list-style: none; } </style>

  • 利用IntersectionObserver实现动态渲染的示例详解

    目录 前言 实现 懒加载组件 长列表组件示意 测试 前言 开发表格时,希望支持可视后的动态加载.在查找资料做了一些尝试后,最终使用IntersectionObserver,相对方便地实现了该功能 IntersectionObserver诞生已经有几年了,所以它的兼容性目前已经达到可以使用的程度了.具体兼容程度以及详细API可参考CDN 实现 懒加载组件 核心就是利用了IntersectionObserver的能力,封装了LazyContainer组件,该组件的children,只有在视口中可见时

  • 交叉观察器 IntersectionObserver用法详解

    目录 1. 背景 2. 兼容性 3. 用法 3.1 observe 3.2 unobserve 3.3 disconnect 3.4 takeRecords 注意: 4. callback 参数 5. IntersectionObserverEntry 对象 6. 应用 注意点 1. 背景 网页开发时,不管是在移动端,还是PC端,都有个很重要的概念,叫做动态懒加载,适用于一些图片资源(或者数据)特别多的场景中,这个时候,我们常常需要了解某个元素是否进入了“视口”(viewport),即用户能不能

  • IntersectionObserver API 详解篇

    温馨提示:本文目前仅适用于在 Chrome 51 及以上中浏览. 2016.11.1 追加,Firefox 52 也已经实现. 2016.11.29 追加,Firefox 的人担心目前规范不够稳定,未来很难保证向后兼容,所以禁用了这个 API,需要手动打开 dom.IntersectionObserver.enabled 才行. IntersectionObserver API 是用来监视某个元素是否滚动进了浏览器窗口的可视区域(视口)或者滚动进了它的某个祖先元素的可视区域内.它的主要功能是用来

  • IntersectionObserver判断是否在可视区域详解

    目录 前言 概念 使用 注意 前言 在日常的开发过程中,有时候会有一些要求判断节点是否进入可视化区域的需求.例如:判断信息是否在可视区域曝光再进行曝光,图片懒加载等.通常我们可以通过scroll和getBoundingclient来进行判断. el.offsetTop - document.documentElement.scrollTop <= 视口高度 // 或 el.getBoundingClientRect().top <= 视口高度 但是scroll,getBoundingclien

  • 可视化埋点平台元素曝光采集intersectionObserver思路实践

    目录 正文 设计方案 测验结论 完整的伪代码 正文 最近一直在开发可视化埋点系统,其中元素的曝光埋点,就是借助了 intersectionObserver 这个原生 api.也是网上推荐度比较高的方案,同时 2022 年,该 api 兼容性也已经很高,同时也有 polyfill,基本上使用无虞. intersectionObserver 本身 api 非常简单,但是在实际使用的过程中,由于可视化埋点的一些特殊性以及对埋点准确性的要求,还是遇到了一些 dom 变更后的边缘场景,本文便是对这些边缘场

  • js如何实现元素曝光上报

    进行数据上报的时候,经常会遇到列表数据曝光上报的问题,只对在当前可视范围内的数据内容进行曝光上报,而对于未在可视范围内的数据不进行曝光上报,等待用户滚动页面或者区域使元素出现在可视范围内时才进行曝光上报. 解决方案 目前针对此类问题,主要有两种解决方案. 方案一:监听页面或者区域scroll事件,通过getBoundingClientRect接口取元素的位置与可视窗口进行判断. function isElementInViewport(el) { var rect = el.getBoundin

  • docker环境搭建JMeter+Grafana+influxdb可视化性能监控平台的教程

    目录 1.安装docker 2.安装及配置influxDB 3.Grafana安装及配置 4.Jmeter配置及压测一个接口 背景: 在用jmeter压测接口的时候发现其原生的监控起来不是很友好,在网上查阅的时候发现结合influxDB和grafana,出来的报告很炫酷,监听结果看起来很舒服很明了. 前言: InfluxDB:是一款用Go语言编写的开源分布式时序数据库.该数据库现在主要用于存储涉及大量的时间戳数据. 小数据量的时候还性能还不错,但是数据量大一点,性能问题就体现出来了.不过只是收集

  • Android RecyclerView曝光采集的实现方法

    本文实例为大家分享了Android RecyclerView曝光采集的具体代码,供大家参考,具体内容如下 一.背景 近期pm提出需要统计首页商品的曝光亮,由于我们的首页是用的recylerview实现的,这里就来讲下如何使用监听recylerview的滚动事件来实现子view的曝光量统计,我们这里说的view都是列表中的子item条目(子view) 二.监听recylerview的滚动事件OnScrollListener onScrollStateChanged:监听滚动状态onScrolled

  • C语言移除元素的三种思路讲解

    目录 问题描述 解题方案 思路一 思路二 思路三(最优解) 问题描述 原题链接:https://leetcode.cn/problems/remove-element/ 解题方案 思路一 思路一: 首先通过简单分析,很明显这是一道顺序表相关问题.首先能够想到的是暴力求解,即思路一:找到所有的val,每次挪动val后的数据覆盖删除val. 代码展示: int find(int*nums,int numsSize,int val) { int i=0; for(i=0;i<numsSize;i++)

  • 详解Android(共享元素)转场动画开发实践

    最近零碎时间一直在研究OpenGL,所以没怎么进行分享,以后可能大部分时间会学习系统底层\NDK\VR\AR等领域,话不多少,今天来分享个小的动画效果. 效果如下 基本知识 其实Android的转场动画由来已久,比如平常开发安卓的时候界面切换 都是右进右出,这样的效果,就是早期的转场动画,在5.0之后安卓官方支持了共享元素的效果,那么问题来了,5.0以后该怎么适配? 准备步骤 定义两个activity,界面跳转是从A到B. ActivityA定义一个控件View,在跳转时传入到Pair里面,详细

  • 如何通过Vue自定义指令实现前端埋点详析

    目录 前言 埋点上报方式都有哪些? 一般对哪些数据做埋点? 需求分析 代码实现 Click 类封装 Exposure 类封装 指令封装 使用 不足 总结 前言 在营销活动中,通过埋点可以获取用户的喜好及交互习惯,从而优化流程,进一步提升用户体验,提高转化率. 在之前的埋点方案实现中,都是在具体的按钮或者图片被点击或者被曝光时主动通过事件去上报埋点.这种方法在项目中埋点比较少时还行,一旦项目中需要大量埋点时,不可避免的要添加很多业务代码.也很大程度上造成了埋点逻辑与业务逻辑的高耦合. 为了改造这种

  • vue项目前端埋点的实现

    埋点方案的确定.业界的埋点方案主要分为以下三类: 代码埋点:在需要埋点的节点调用接口,携带数据上传.如百度统计等: 可视化埋点:使用可视化工具进行配置化的埋点,即所谓的「无痕埋点」,前端在页面加载时,可以读取配置数据,自动调用接口进行埋点.如开源的Mixpanel; 无埋点:前端自动采集全部事件并上报埋点数据.如国内的神策数据等: 在当时排期紧凑,人力紧缺的情况下,显然不允许我们去开发可视化埋点方案和无埋点方案,所以只能采取代码埋点方案. 命令式埋点 命令式埋点,顾名思义,开发者需要手动在需要埋

  • 详解微信小程序(Taro)手动埋点和自动埋点的实现

    每一个公司要想用户增长,都要收集和分析用户操作数据,因此埋点是必不可少的事情. 而对于前端职业发展来说,传统的手动埋点,无疑是繁琐又无聊的事情,能简化就简化. 一.手动埋点 手动埋点就是在每一处需要的地方,都加一段上报埋点的代码.影响代码的阅读体验,且散落的埋点代码不方便管理. 以页面 pv 为例,我们此前是在每一个页面中上报 pv: // src/manual/home/index.tsx import tracking from "./tracking"; // pageSn 是前

  • 快速搭建python爬虫管理平台

    爬虫有多重要 对搜索引擎来说,爬虫不可或缺:对舆情公司来说,爬虫是基础:对 NLP来说,爬虫可以获取语料:对初创公司来说,爬虫可以获取初始内容.但是爬虫技术纷繁复杂,不同类型的抓取场景会运用到不同的技术.例如,简单的静态页面可以用 HTTP 请求+HTML 解析器直接搞定:一个动态页面需要用 Puppeteer 或 Selenium等自动化测试工具:有反爬的网站需要用到代理.打码等技术:等等.那么此时就需要一个成熟的爬虫管理平台,帮助企业或个人处理大量的爬虫类别. 理解什么是爬虫管理平台 定义

随机推荐