前端页面在加载的时候,如果数据量过大,会导致页面白屏时间过长,后端生成模板的方式,白屏时间大量消耗在解析DOM结构;如果使用前端框架,页面DOM结构简单;但是项目内会存在大量接口请求并创建渲染视图, 如果单页面功能复杂,数据量大,渲染的结构复杂时,会出现大量等待效果。
如果能对元素进行监听,当元素出现在可视区域时,才会做相关操作,不在可视区域时,可以限制相关功能或数据更新等。
对于Android系统或者谷歌等内核提供 IntersectionObserver 接口; 其他不兼容的可以时间监听 onscroll事件,实时去校验元素是否在可视区域范围内。
1. IntersectionObserver 接口
初始化监听器
var intersectionObserver = new IntersectionObserver(function(entries) {
// entries 包含被监听的元素对象集合
for(var i=0,l=entries.length;i<l;i++){
console.log(entries[i]);
// 判断是否进入可视区域的判断条件: intersectionRatio > 0
// intersectionRatio 0 : 标识未进入可视区域 0.5: 有一半进入可视区域 1: 完全进入可视区域
// target 当前监听的元素ElementHTML
}
});
开始监听元素
// 设置需要监听的元素ElementHTML, 当前监听的元素会在上面监听器内的entries集合内
intersectionObserver.observe(document.querySelector('.scrollerFooter'));
intersectionObserver.observe(document.querySelector('.test'));
取消监听元素
// 取消监听需要设置上面监听的元素ElementHTML
intersectionObserver.unobserve(document.querySelector('.scrollerFooter'));
intersectionObserver.unobserve(document.querySelector('.test'));
销毁监听器
// 调用取消监听的接口
intersectionObserver.disconnect();
2. 兼容性监听器实现 (通过监听滚动事件,检测元素与父节点的可视化区域的范围值比较, 无法兼容子节点内的子节点位置问题)
初始化监听器
// 需要监听的元素
var elements = [];
var rootElement = window; // 需要监听的根元素, 默认监听window的滚动
// 监听器, 避免触发频繁可以使用防抖函数, 这里暂时不介绍
var onScrollTrigger = function(e){
for(var i=0,l=elements.length;i<l;i++){
calculation(elements[i]);
}
}, checkElementVisible = function(elm){
var visible = false;
var parent = elm.parentNode;
// 检测是否属于根元素的可视范围
while (parent && parent !== rootElement && parent !== w.document) {
// ES6解构语法
// const { offsetWidth, offsetHeight, offsetTop, offsetLeft, scrollLeft, scrollTop } = parent;
//const {
//offsetTop: oT,
//offsetLeft: oL,
//offsetWidth: Wh,
//offsetHeight: Hg,
//top = oT - offsetTop - scrollTop,
//bottom = top + Hg,
//left = oL - offsetLeft - scrollLeft,
//right = left + Wh,
//} = elm;
//const width = offsetWidth;
//const height = offsetHeight;
var top = elm.offsetTop - parent.offsetTop - parent.scrollTop,
bottom = top + elm.offsetHeight,
left = elm.offsetLeft - parent.offsetLeft - parent.scrollLeft,
right = left + elm.offsetWidth;
var width = parent.offsetWidth;
var height = parent.offsetHeight;
// 校验位置, 当前判断规则属于,只要存在一角出现在可视区域内, 如果要校验完整出现在可视区域内, 可以使用 top>=0 && left>=0 && bottom<=height && right<=width的判断逻辑
visible = ((top >= 0 && top <= height) || (bottom <= height && bottom >= 0))
&& ((left >= 0 && left <= width) || (right <= width && right >= 0));
if (!visible) { // 不在范围
break;
}
elm = parent;
parent = elm.parentNode;
}
// 校验根节点
if (visible) {
const { top, bottom, left, right } = elm.getBoundingClientRect();
const width = rootElm.innerWidth || rootElm.clientWidth;
const height = rootElm.innerHeight || rootElm.clientHeight;
visible = ((top >= 0 && top <= height) || (bottom <= height && bottom >= 0))
&& ((left >= 0 && left <= width) || (right <= width && right >= 0));
}
return visible;
}, calculation = function(elm){
// 进入可视区域
const visible = checkElementVisible(elm);
// 触发事件 elm: 监听的元素 visible: 当前是否在可视区域 true/false
};
// 监听滚动事件onscroll
if (window.addEventListener) {
rootElement.addEventListener('scroll', onScrollTrigger, false);
} else if (window.attachEvent) {
rootElement.attachEvent('onscroll', onScrollTrigger);
} else {
rootElement.onscroll = function () {
onScrollTrigger.apply(this, arguments);
};
}
监听元素
// 放入监听的元素内
var observer = function(elm){
elements.push(elm);
}
observer(document.querySelector('.scrollerFooter'));
observer(document.querySelector('.test'));
取消监听的元素
// 取消监听
var unobserve = function(elm){
for(var index,i=0,l=elements.length;i<l;i++){
if(elements[i] === elm){
index = i;
break;
}
}
index >= 0 && elements.splice(index, 1);
}
unobserver(document.querySelector('.scrollerFooter'));
unobserver(document.querySelector('.test'));
销毁监听器
// 移除监听滚动事件onscroll
if (window.removeEventListener) {
window.removeEventListener('scroll', onScrollTrigger);
} else if (window.detachEvent) {
window.detachEvent('onscroll', onScrollTrigger);
} else {
window.onscroll = null;
}
以上就是两种方式监控元素是否进入可视区域或离开可视区域的代码了
结论:
PS: 经过测试, 建议不要监听window的滚动事件; 滚动监听的方式效果会存在延时计算;暂时还没有其他解决方案, 使用定时器太耗资源, 无法监听过多的元素;