ElementUI 官网 InfiniteScroll 使用:https://element.eleme.cn/#/zh-CN/component/infiniteScroll
首先先叙述一下需求,说明文章总体内容:
需求: 页面滚动到底部,触发接口数据加载,实现下滑无限滚动效果。
首先为了更好的说明问题,和后续的解决方案,先说一下 ElementUI 组件的使用情况。整体的使用参照官方文档即可,使用还是很简单的,关键代码概览:
(1)首先是 Vue 部分:
<template>
<div class="main-iframe" v-infinite-scroll="load">
// 内容
div>
template>
<script>
export default {
methods: {
load () {
// 调用接口
}
}
}
script>
(2)如果要实现无限滚动的,最关键点是要有以下 CSS:
.main-iframe {
height: calc(100vh);
overflow: auto;
// 其他CSS....
}
也就是 height
和 overflow:auto
。换句话说就是,在一个页面中存在一个给定高度 height
的盒子(无论 height 有多高),当给定 overflow:auto
的时候,就会在数据超过盒子本身高度 height
的时候,出现一个滚滑轮。此时 ElementUI 无限加载组件就会判断这个滚滑轮,当它滑动到这个盒子的底部的时候,触发 load 方法。本质就是下图这样的效果。
那假设这个页面,就只有这个盒子,那 height 设置成 calc(100vh) 占据视口100% ,就可以达到好像滑轮到了整个页面的底部实现加载的效果。当然如果该页面还有其他内容,那 height 做出相应调整即可。因此它的核心是到了这个盒子的底部。
随后在这个第一版的实现方式下,如果要回到顶部,那就是让这个盒子的滑轮回到顶部。
document.getElementsByClassName('main-iframe')[0].scrollTo({
top: document.getElementById('content').offsetTop,
behavior: 'smooth'
});
而目前虽然确实已经实现了功能,但到了具体的使用中发现,部分浏览器存在滚动失效的情况(滚动了几次后,就无法再次滚动),但不确定哪些浏览器会失效或者失效的原因,但大致可推断为浏览器兼容性问题,或者受到了相同浏览器不同版本的影响。
如果您依然要使用 ElementUI 组件的话,为了避免大篇幅引用,您可以前往该文章参考:
如果还是会出现问题的话,那就不妨试试接下来的方案,不再使用 ElementUI 的滚动组件,而是直接使用 JS 手写这样的功能,笔者目前测试没问题,兼容性问题也只存在于太过老旧的浏览器上。
首先如果要手动实现滚动效果,那肯定要监听滚动条事件。在 Vue 中,其实有两种选择,一种是监听 window 滚动条事件,另一种是监听某个具体元素的 @scroll 滚动条事件。但因为笔者目前需求的页面,只有这个需要监听滚动的元素,所以使用第一种方式。
而第二种方式因为笔者没有具体去实现,所以在这里没有相关实现代码,各位有需要可以参考下列方案去尝试。
监听事件:
mounted() {
window.addEventListener('scroll', this.handleScroll);
},
beforeDestroy() {
window.removeEventListener('scroll', this.handleScroll);
},
而如果要实现第一种方式,那就代表着要监听整个页面的滚动条,换句话说,滚动时新增的数据,要撑起整个页面的 height
,不要让这个盒子的高度固定。再具体来说,就是从 CSS 入手:
.main-iframe {
min-height: calc(100vh);
// 其他CSS...
}
也就是 min-height
。换句话说就是整个盒子的最小高度是视口100%,当整个页面的滚动滑轮到了底部,就去调用接口,并添加数据给这个盒子。随后这个盒子的高度和页面的高度就会同步撑起。本质就是下图这样的效果。一定要注意,当前情况和使用 ElementUI 无限滚动组件时有本质区别。
而如果要实现第二种 @scroll 方式的话,CSS 依然至少是 height + overflow:auto。
第一种方式具体实现的 JS 就是:
handleScroll() {
// 滚动条在Y轴上的滚动距离
let scrollTop = 0; let bodyScrollTop = 0; let documentScrollTop = 0;
if (document.body) {
bodyScrollTop = document.body.scrollTop;
}
if (document.documentElement) {
documentScrollTop = document.documentElement.scrollTop;
}
scrollTop = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop;
// 浏览器视口的高度
let windowHeight = 0;
if (document.compatMode === 'CSS1Compat') {
windowHeight = document.documentElement.clientHeight;
} else {
windowHeight = document.body.clientHeight;
}
// 文档的总高度
let scrollHeight = 0; let bodyScrollHeight = 0; let documentScrollHeight = 0;
if (document.body) {
bodyScrollHeight = document.body.scrollHeight;
}
if (document.documentElement) {
documentScrollHeight = document.documentElement.scrollHeight;
}
scrollHeight = (bodyScrollHeight - documentScrollHeight > 0) ? bodyScrollHeight : documentScrollHeight;
if (scrollTop + windowHeight > scrollHeight - 50) {
this.load(); // 调用接口
}
}
而相对的,如果是第二种 @scroll 的方式,那就肯定不是像上面这样拿 window 相关的高度。而是需要去拿到:滚动条在当前元素上的滚动距离、该元素的固定高度、该元素的总高度。
此时回到顶部的功能,因为和最开始滑轮的情况不同,所以回到顶部功能需要重新写,简单来说就是要让 window 的滑轮回到顶部(这里参考 ElementUI 回到顶部的源码,手写了一个回到顶部的效果):
toTop() {
let bodyScrollTop = 0;
let documentScrollTop = 0;
if (document.body) {
bodyScrollTop = document.body.scrollTop;
}
if (document.documentElement) {
documentScrollTop = document.documentElement.scrollTop;
}
let s = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop;
let step = 0;
const interval = setInterval(() => {
if (s <= 0) {
clearInterval(interval);
return;
}
step += 10;
s -= step;
window.scrollTo(0, s);
}, 20);
}
到这里为止的参考文章:
而使用第一种方式后,目前又遇到了两个坑。相关问题和解决方案如下。
第一个问题是:判断页面滑轮到底部的方式。判断依据理论上是:
scrollTop + windowHeight === scrollHeight
也就是
滚动条在Y轴上滚动的距离 + 浏览器视口的高度 === 页面总高度
但是这么做之后,发现在部分浏览器中依然是失效的。不过失效原因通过 console.log 输出这三个值,很快就能定位到问题。问题就是,这个等式不可能完全成立,在实际不同浏览器的测试中发现,到达底部时,可能会出现: scrollTop + windowHeight < scrollHeight
,而这个差值在小数级别。
所以,为了解决这个问题,在上面的代码中,我设置了一个 50 的阈值,也就是:
scrollTop + windowHeight > scrollHeight - 50
。
这么设置之后,问题基本迎刃而解(当然这个阈值可以自己随意设定)。
第二个问题是:如果首屏数据没有超过页面 / 盒子高度,导致该部分没出现滑轮怎么办。
因为现在是出现滑轮后,滚动滑轮才会调用 handleScroll()
,然后进行一系列的判断。
其实解决方案也足够简单,代码就不展示了。解决方案其实有两种:
第一种:因为无限滚动组件在实现时,我们底部可能增加了一个 “下滑加载更多” 或 “已经滑到底部” 的提示语,那我们可以在没出现滚轮的时候,调整提示语,让使用者点击什么按钮,从而手动加载数据。但是这种方式感觉还是不太好。(判断何时没出现滚轮,在下面第二种方法上有写)
第二种是目前我使用的办法,说一下思路:当首屏接口调用完成时,手动调用一下 handleScroll
,如果 scrollTop + windowHeight === scrollHeight
(经过测试,就算是不同浏览器,如果数据量不够多,导致没能出现页面滑轮,这个等式也会在此时成立。当然如果此式真的不相等,也可以根据不同浏览器不相等的情况,给出一个合理的阈值,再去判断),那就意味着此时页面没出现滑轮,那就需要继续调用接口,直到页面出现滑轮。出现滑轮后,这个等式就肯定不会成立,此时跳出循环,不再调用接口即可。