想获取更多原创好文,请搜索公众号关注我们吧~ 本文首发于政采云前端博客:一次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:
- 如果目标元素出现在 root 可视区,返回 true。
- 如果从 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));
}
}
参考文献
推荐阅读
开源作品
- 政采云前端小报
开源地址 www.zoo.team/openweekly/ (小报官网首页有微信交流群)
- 商品选择 sku 插件
开源地址 github.com/zcy-inc/sku...
招贤纳士
政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。政采云前端团队(ZooTeam),一个年轻富有激情和创造力的前端团队。团队现有 80 余个前端小伙伴,平均年龄 27 岁,近 4 成是全栈工程师,妥妥的青年风暴团。成员构成既有来自于阿里、网易的"老"兵,也有浙大、中科大、杭电等校的应届新人。团队在日常的业务对接之外,还在物料体系、工程平台、搭建平台、智能化平台、性能体验、云端应用、数据分析、错误监控及可视化等方向进行技术探索和实战,推动并落地了一系列的内部技术产品,持续探索前端技术体系的新边界。
如果你想改变一直被事折腾,希望开始能折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊... 如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的前端团队的成长历程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 ZooTeam@cai-inc.com