当前位置:首页 >探索 >一次DOM曝光封装历程 然后我就发现了新大陆

一次DOM曝光封装历程 然后我就发现了新大陆

2024-06-30 23:48:22 [百科] 来源:避面尹邢网

一次DOM曝光封装历程

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

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

初版

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

一次DOM曝光封装历程 然后我就发现了新大陆

具体代码如下:

一次DOM曝光封装历程 然后我就发现了新大陆

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

一次DOM曝光封装历程 然后我就发现了新大陆

其中两个点

第一个:

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

其中这里的历程 top 以及其他边距对应视口计算方式可能和你想象的不一样,上图摘自 你真的曝光会用getBoundingClientRect 吗 (https://juejin.im/entry/59c1fd23f265da06594316a9), 它对这个属性讲的比较详细可以看看

第二个:

let elEnter = false; //

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

重写

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

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

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

window.IntersectionObserver

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

关于他的具体介绍,我这里简单讲一下我用到的属性,具体可查阅 超好用的 API 之 IntersectionObserver (https://juejin.im/post/5d11ced1f265da1b7004b6f7) 或者 MDN (https://developer.mozilla.org/zh-CN/docs/Web/API/IntersectionObserver)

主要使用如下:

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

主要两点

1.options 的配置为:

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

2.callback 函数如下:

(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 横向移动 (https://jsbin.com/vuzujiw/6/edit?html,css,js,console,output),我第一调试的时候就碰到了

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

最终版

考虑兼容性:

// 使用w3c出的polyfillrequire('intersection-observer');

主要逻辑如下:

/** * 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 吗(https://juejin.im/entry/59c1fd23f265da06594316a9)

责任编辑:武晓燕 来源: 政采云技术 DOM曝光封装

(责任编辑:时尚)

    推荐文章
    热点阅读