• 【ElementUI】InfiniteScroll 无限滚动组件在部分浏览器中滚动失效 的 解决方案


    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    (2)如果要实现无限滚动的,最关键点是要有以下 CSS:

    .main-iframe {
       height: calc(100vh);
       overflow: auto;
       // 其他CSS....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    也就是 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'
    });
    
    • 1
    • 2
    • 3
    • 4

    而目前虽然确实已经实现了功能,但到了具体的使用中发现,部分浏览器存在滚动失效的情况(滚动了几次后,就无法再次滚动),但不确定哪些浏览器会失效或者失效的原因,但大致可推断为浏览器兼容性问题,或者受到了相同浏览器不同版本的影响。

    如果您依然要使用 ElementUI 组件的话,为了避免大篇幅引用,您可以前往该文章参考:

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

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


    JS 实现无限滚动效果

    首先如果要手动实现滚动效果,那肯定要监听滚动条事件。在 Vue 中,其实有两种选择,一种是监听 window 滚动条事件,另一种是监听某个具体元素的 @scroll 滚动条事件。但因为笔者目前需求的页面,只有这个需要监听滚动的元素,所以使用第一种方式。

    而第二种方式因为笔者没有具体去实现,所以在这里没有相关实现代码,各位有需要可以参考下列方案去尝试。

    监听事件

    mounted() {
      window.addEventListener('scroll', this.handleScroll);
    },
    beforeDestroy() {
      window.removeEventListener('scroll', this.handleScroll);
    },
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    而如果要实现第一种方式,那就代表着要监听整个页面的滚动条,换句话说,滚动时新增的数据,要撑起整个页面的 height,不要让这个盒子的高度固定。再具体来说,就是从 CSS 入手:

    .main-iframe {
       min-height: calc(100vh);
       // 其他CSS...
    }
    
    • 1
    • 2
    • 3
    • 4

    也就是 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(); // 调用接口
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    而相对的,如果是第二种 @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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    到这里为止的参考文章:

    判断滚动条是否到了底部

    vue监听页面滚动事件


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


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

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

    scrollTop + windowHeight === scrollHeight

    也就是

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

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

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

    scrollTop + windowHeight > scrollHeight - 50

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


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

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

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

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

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

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

  • 相关阅读:
    尚医通项目124-149:微信登录以及阿里云OSS的使用
    干掉 “重复代码”,这三种方式绝了!
    C陷阱和缺陷 第3章 语义“陷阱” 3.2 非数组的指针
    AgentGPT:基于GPT-4的开源AI自动化机器人工具
    顺序栈的入栈出栈
    MVCC实现过程
    Element Plus中Cascader 级联选择器(选择任意一级选项 - 更改下拉框选中方式)
    Java项目:SSM图书馆图书管理借阅书籍管理系统
    HTTP1.x , 2.0 , 3.0 版本之间的对比
    RabbitMQ之死信队列解读
  • 原文地址:https://blog.csdn.net/qq_45613931/article/details/127422347