前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一次DOM曝光封装历程

一次DOM曝光封装历程

作者头像
政采云前端团队
发布2023-09-01 19:52:46
1880
发布2023-09-01 19:52:46
举报
文章被收录于专栏:采云轩

一次DOM曝光封装历程 http://zoo.zhengcaiyun.cn/blog/article/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 吗 (https://juejin.im/entry/59c1fd23f265da06594316a9), 它对这个属性讲的比较详细可以看看

第二个:

代码语言:javascript
复制
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)

主要使用如下:

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

用户要看的子元素是被父元素给限制住了,但是对于 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 吗(https://juejin.im/entry/59c1fd23f265da06594316a9)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-08-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 政采云技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初版
    • 其中两个点
    • 重写
      • window.IntersectionObserver
        • 主要两点
    • 最终版
    • 参考文献
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档