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

 更新时间:2023年1月6日 14:47  点击:532 作者:芋仔

正文

最近一直在开发可视化埋点系统,其中元素的曝光埋点,就是借助了 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元素曝光采集的资料请关注猪先飞其它相关文章!

原文出处:https://juejin.cn/post/7126739870214520868

[!--infotagslink--]

相关文章

  • 使用C# CefSharp Python采集某网站简历并且自动发送邀请短信的方法

    这篇文章主要给大家介绍了关于如何使用C# CefSharp Python采集某网站简历并且自动发送邀请短信的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧...2020-06-25
  • asp.net采集网页图片的具体方法

    采集网页上图片的主要关键是在怎么解析出页面代码里那些img标签的src属性...2021-09-22
  • 音乐采集程序

    <?php header("Content-Type: text/html; charset=gb2312"); session_start(); set_time_limit(0); $str = file_get_contents('http://music.soso.com/'); pr...2016-11-25
  • 利用fopen函数采集新闻页面内容保存到本地函数

     <? / / PHP的新闻抓取由Neil Moomey,。 / /你可以自由的使用此代码作为您的愿望。 / /请确保您可以从任何网站,你抓从标题许可。 / /你可能需要写上您的服务器上...2016-11-25
  • php几种采集远程服务器内容代码

    //方法一模仿用户访问网页 代码如下 复制代码 function readpr($link,$url) { $fp = fsockopen ($url, 80, $errno, $errstr, 30); if (!...2016-11-25
  • 如何采集搜狗微信搜索的内容(SogouEncrypt版) -- hzw

    在搜狗微信搜索中,之前微信的内容是用 http://weixin.sogou.com/gzhjs?cb=sogou.weixin.gzhcb&openid=oIWsFt1OaL2XHiDx6809O8q2KZ5A&page=1 这类格式来调用数据的,openid...2016-05-19
  • Php CURL模拟登陆论坛并采集数据实例

    本文章来给各位同学介绍一下关于Php CURL模拟登陆论坛并采集数据实例,如果你对利用curl模拟登录功能有兴趣可进入参考。 要模拟浏览器访问网站,首选要学会观察浏览...2016-11-25
  • PHP采集远程图片到本地实现代码

    在php中要保存远程图片到自己服务器本地,我们需要先正则字符串中的内容图片,然后再利用相关函数把图片读取并保存到本地硬盘即可。 代码如下 复制代码 ...2016-11-25
  • 搭建海量数据采集爬虫框架教程

    海量数据采集爬虫,相当于大的搜索引擎爬虫了,现在我们来看看如何搭建海量数据采集爬虫框架,相关的朋友可以参考一下。 随着BIG DATA大数据概念逐渐升温,如何搭建一个...2016-11-25
  • apache如何禁止网络爬虫采集的配置方法

    Apache中禁止网络爬虫,其实也挺简单的,只要把下面的代码配置到apache的httpd.conf文件中的Location中,就可以了。 <Location /> SetEnvIfNoCase User-Agent "spider" bad_b...2016-01-28
  • 网站内容采集到底有没有用

    经过百度的几次小更新,收录并不乐观,一直才10多篇的样子,就连google收录都不怎么样,自己对于网站的收录还是很看重的,但是最终的结果却不令我乐观,自己想了想原因,应该就是因...2017-07-06
  • 防采集方法

    这个防采集的方法是我今天在无奈之下想出来的,要开网站一看我晕不能访问,立马跑到服务器上一看,apache点N高的内存,后来在一急之下就想出了这种办法,呵呵,到底是什么方...2017-07-06
  • C# 利用AForge实现摄像头信息采集

    这篇文章主要介绍了C# 如何利用AForge实现摄像头信息采集,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下...2020-11-03
  • DEDE采集大师官方留后门的删除办法

    <?php教程 require_once(dirname(__file__)."/../include/common.inc.php"); if(emptyempty($dopost)) { $dopost = ""; } if($dopost=="rename") {...2016-11-25
  • PHP html dom php+正则 采集文章代码

    <?php //包含PHP Simple html Dom 类库文件 include_once('./simplehtmldom/simple_html_dom.php'); //采集html function getwebcontent($url){ $ch = cu...2016-11-25
  • php 网页采集入库程序代码

    网页采集现在用到最多是工具了,像最受站长欢迎的就是火车头了,但有一些站长喜欢使用网页来自定义采集了,下面一起来看一个php 网页采集入库程序代码 php 网页采集程...2016-11-25
  • PHP命令行采集所有股票趋势信息程序

    股票信息我们做理财网站都只有采集门户站的数据不可能自己生成股票信息了,这个就会要用到抓取股票站的数据了,下面我们来看一篇关于PHP命令行采集所有股票趋势信息程序...2016-11-25
  • 利用PHP命令行模式采集股票趋势信息

    本文介绍的是一个简单的股票采集工具,采集的同花顺股票趋势信息,使用php命令行模式编写。只完成了采集部分功能,将输出的数据复制到execl(或ET)分析,比任何后台都方便。下面一起来学习学习。...2016-08-27
  • php采集代码-反防盗链采集

    很多php新手在开发自己的网站采集功能时都会直接用到file_get_contents来读取或fopen是吧,是吧,我们下载采集功能加强了了一点点就是要对方的防盗链都不能防止的采集功...2016-11-25
  • php curl网站采集的实现程序

    网站采集功能现在多半会使用火车头这些软件来实现了,但是对于一些定时或小的采集我们可以使用程序来实现,在php中curl是当选的一个函数了,下面一起来看看curl网站采集的...2016-11-25