ElementUI 官网 InfiniteScroll 使用:https://element.eleme.cn/#/zh-CN/component/infiniteScroll

首先先叙述一下需求,说明文章总体内容:

需求: 页面滚动到底部,触发接口数据加载,实现下滑无限滚动效果。



Vue 项目使用 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....
}

也就是 heightoverflow: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 组件的话,为了避免大篇幅引用,您可以前往该文章参考:

无限滚动 仅触发1次或几次 无效 可行 解决方案

如果还是会出现问题的话,那就不妨试试接下来的方案,不再使用 ElementUI 的滚动组件,而是直接使用 JS 手写这样的功能,笔者目前测试没问题,兼容性问题也只存在于太过老旧的浏览器上。


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);
}

到这里为止的参考文章:

判断滚动条是否到了底部

vue监听页面滚动事件


而使用第一种方式后,目前又遇到了两个坑。相关问题和解决方案如下。


问题一:判断页面滑轮到底部的方式

第一个问题是:判断页面滑轮到底部的方式。判断依据理论上是:

scrollTop + windowHeight === scrollHeight

也就是

滚动条在Y轴上滚动的距离 + 浏览器视口的高度 === 页面总高度

但是这么做之后,发现在部分浏览器中依然是失效的。不过失效原因通过 console.log 输出这三个值,很快就能定位到问题。问题就是,这个等式不可能完全成立,在实际不同浏览器的测试中发现,到达底部时,可能会出现: scrollTop + windowHeight < scrollHeight ,而这个差值在小数级别。

所以,为了解决这个问题,在上面的代码中,我设置了一个 50 的阈值,也就是:

scrollTop + windowHeight > scrollHeight - 50

这么设置之后,问题基本迎刃而解(当然这个阈值可以自己随意设定)。


问题二:如果没有超过页面/盒子高度,导致没出现滑轮怎么办

第二个问题是:如果首屏数据没有超过页面 / 盒子高度,导致该部分没出现滑轮怎么办。

因为现在是出现滑轮后,滚动滑轮才会调用 handleScroll(),然后进行一系列的判断。

其实解决方案也足够简单,代码就不展示了。解决方案其实有两种:

第一种:因为无限滚动组件在实现时,我们底部可能增加了一个 “下滑加载更多” 或 “已经滑到底部” 的提示语,那我们可以在没出现滚轮的时候,调整提示语,让使用者点击什么按钮,从而手动加载数据。但是这种方式感觉还是不太好。(判断何时没出现滚轮,在下面第二种方法上有写)

第二种是目前我使用的办法,说一下思路:当首屏接口调用完成时,手动调用一下 handleScroll,如果 scrollTop + windowHeight === scrollHeight (经过测试,就算是不同浏览器,如果数据量不够多,导致没能出现页面滑轮,这个等式也会在此时成立。当然如果此式真的不相等,也可以根据不同浏览器不相等的情况,给出一个合理的阈值,再去判断),那就意味着此时页面没出现滑轮,那就需要继续调用接口,直到页面出现滑轮。出现滑轮后,这个等式就肯定不会成立,此时跳出循环,不再调用接口即可。

Logo

前往低代码交流专区

更多推荐