一次DOM曝光封装历程

想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客:一次DOM曝光封装历程

随着最近曝光埋点的需求越来越频繁,就想把之前的写好的曝光逻辑抽出来封装了一下作为公用。

初版

逻辑:window.scroll 监听滚动 + 使用 getBoundingClientRect() 相对于视口位置实现

具体代码如下:

javascript 复制代码
function buryExposure (el, fn) {
    /*
    * 省略一些边界判断
    * ......
    **/
    let elEnter = false; // dom是否进入可视区域
    el.exposure = () => {
        const { top } = el.getBoundingClientRect();
        if (top > 0 && top < window.screen.availHeight) {
            if (!elEnter) {
                elEnter = true;
                fn(el)
            }
        } else {
            elEnter = false;
        };
    }
    document.addEventListener('scroll', el.exposure);
}

回调传出 el ,一般为页面注销时注销对应滚动事件: el.exposure

其中两个点

第一个:

javascript 复制代码
// 判断上边距出现在视口内,则判定为曝光
const { top } = el.getBoundingClientRect();
if (top > 0 && top < window.screen.availHeight)

其中这里的 top 以及其他边距对应视口计算方式可能和你想象的不一样,上图摘自你真的会用getBoundingClientRect吗,它对这个属性讲的比较详细可以看看

第二个:

javascript 复制代码
let elEnter = false; //

用一个变量来控制当dom已经曝光则不再持续,直到 dom 离开视口,重新计算

重写

当我以为已经够用时,某次需求需要监听 DOM 在某个 div 内横向滑动的曝光,发现它并不支持!而后面一些曝光策略对比的文章说到这个 getBoundingClientRect API 会引起性能问题

不相信的你可以试一下!!!

于是我就开启 google 大法和在掘金社区内搜一些曝光的文章,然后我就发现了新大陆!

window.IntersectionObserver

这次曝光的主角:优先使用异步观察目标元素与祖先元素或顶级文档 viewport 的交集中的变化的方法

关于他的具体介绍,我这里简单讲一下我用到的属性,具体可查阅超好用的API之IntersectionObserver或者MDN

主要使用如下:

javascript 复制代码
const io = new IntersectionObserver(callback, options)
io.observe(DOM)
  • callback 回调函数,options 是配置参数
  • io.observe 观察函数,DOM 为被观察对象

主要两点

1.options的配置为:

javascript 复制代码
const observerOptions = {
    root: null,  // 默认使用视口作为交集对象
    rootMargin: '0px', // 无样式
    threshold: [...Array(100).keys()].map(x => x / 100) // 监听交集时的每0.01变化触发callback回调
}

2.callback 函数如下:

javascript 复制代码
(entries) => {
    // 过程性监听
    entries.forEach((item) => {
        if (item.intersectionRatio > 0 && item.intersectionRatio <= 1) { // 部分显示即为显示
            // todo....
        } else if (item.intersectionRatio === 0) { // 不可见时恢复
            // todo...
        }
    });
}

曝光的判断来自以下第二种(部分显示则曝光):

  • intersectionRatio === 1:则监听对象完整显示
  • intersectionRatio > 0 && intersectionRatio < 1 : 则监听对象部分显示
  • intersectionRatio === 0:则监听对象不显示 其实 entries[] 子元素对象还有一个属性:isIntersecting

返回一个布尔值,下列两种操作均会触发 callback:

  1. 如果目标元素出现在 root 可视区,返回 true。
  2. 如果从 root 可视区消失,返回 false

按理说应该是使用它,但是发现不适合现实场景!!!

比如类banner 横向移动,我第一调试的时候就碰到了

用户要看的子元素是被父元素给限制住了,但是对于 isIntersecting 它来讲是出现在视口内的。

最终版

考虑兼容性:

javascript 复制代码
// 使用w3c出的polyfill
require('intersection-observer');

主要逻辑如下:

javascript 复制代码
/**
 * DOM曝光
 * @param {object} options 配置参数
 * options @param {Array} DOMs 要被监听的DOM列表
 * options @param {Function} callback[type, io] 回调,传入参数
 * options @param {DOM} parentDom 子元素的对应父元素
 */
export default function expose (options = {}) {
    if (!options.DOMs || !options.callback) {
        console.error('Error: 传入监听DOM或者回调函数');
        return;
    }
    const observerOptions = {
        root: null,
        rootMargin: '0px',
        threshold: [...Array(100).keys()].map(x => x / 100)
    };
    options.parentDom && (observerOptions.root = options.parentDom);
    // 优先使用异步观察目标元素与祖先元素或顶级文档viewport的交集中的变化的方法
    if (window.IntersectionObserver) {
        let elEnter = false; // dom是否进入可视区域
        const io = new IntersectionObserver((entries) => {
            // 回调包装
            const fn = () => options.callback({ io });
            // 过程性监听
            entries.forEach((item) => {
                if (!elEnter && item.intersectionRatio > 0 && item.intersectionRatio <= 1) { // 部分显示即为显示
                    fn();
                    elEnter = true;
                } else if (item.intersectionRatio === 0) { // 不可见时恢复
                    elEnter = false;
                }
            });
        }, observerOptions);
        // 监听DOM
        options.DOMs.forEach(DOM => io.observe(DOM));
    }
}

参考文献

你真的会用getBoundingClientRect

推荐阅读

漫谈基建

Vue 2 模版编译流程详解

初探 chatgpt

前端常见问题分析

花里胡哨的背景渐变

开源作品

  • 政采云前端小报

开源地址 www.zoo.team/openweekly/ (小报官网首页有微信交流群)

  • 商品选择 sku 插件

开源地址 github.com/zcy-inc/sku...

招贤纳士

政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队。团队现有 80 余个前端小伙伴,平均年龄 27 岁,近 4 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的"老"兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、智能化平台、性能体验、云端应用、数据分析、错误监控及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。

如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊... 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com

相关推荐
chinahcp200816 分钟前
CSS保持元素宽高比,固定元素宽高比
前端·css·html·css3·html5
gnip1 小时前
浏览器跨标签页通信方案详解
前端·javascript
gnip2 小时前
运行时模块批量导入
前端·javascript
hyy27952276842 小时前
企业级WEB应用服务器TOMCAT
java·前端·tomcat
逆风优雅2 小时前
vue实现模拟 ai 对话功能
前端·javascript·html
若梦plus3 小时前
http基于websocket协议通信分析
前端·网络协议
不羁。。3 小时前
【web站点安全开发】任务3:网页开发的骨架HTML与美容术CSS
前端·css·html
这是个栗子3 小时前
【问题解决】Vue调试工具Vue Devtools插件安装后不显示
前端·javascript·vue.js
姑苏洛言3 小时前
待办事项小程序开发
前端·javascript
百万蹄蹄向前冲3 小时前
让AI写2D格斗游戏,坏了我成测试了
前端·canvas·trae