前端 | 页面触底自动加载 Vue 组件

不管是 web 端还是移动端,信息流都是现在很流行的信息展示方式。信息流经常搭配自动加载一起使用以获得更好的使用体验。

最近在使用 Vue 开发过程中也遇到了首页信息流自动加载的需求。大致了解了一下几个滚动自动加载组件,发现多数都是把内容放在在一个单独的滚动容器内;但我遇到的需求是整个页面的滚动(博客列表首页那种),不是限制在容器内,不太符合。把整个页面放进滚动容器明显很奇怪,只是为了一个简单的下拉加载不值当。所以参考网上的一些介绍实现了一个几十行的简单小组件 ButtomDetector 来实现这个功能,同时也方便在多个需要到底自动加载的页面中进行复用。

实现原理

实现原理非常简单:

  • 监听页面的滚动事件
  • 在触发滚动时,调用 DOM api 来获取页面的滚动状态
  • 如果已经滚动到底,抛出事件供页面使用

JS 事件监听

// 监听 scroll 事件,绑定回调函数
window.addEventListener('scroll', this.listenBottomOut)

// 取消事件监听
window.removeEventListener('scroll', this.listenBottomOut, false)

页面滚动状态

DOM 提供了获取页面元素滚动状态的相关属性,这里主要用到以下三个:scrollHeight 滚动内容高度,scrollTop 滚动内容顶部离显示区域的距离,clientHeight 显示区域高度。如下图所示。

前端 | 页面触底自动加载 Vue 组件

因此,判断滚动到底就很简单了:

if (scrollTop + clientHeight >= scrollHeight - delta) {
    // emit bottom event
}

其中 delta 的作用是可以在快到底端(例如还有50px)时提前进行加载,避免拉到底后等待加载的这段时间。

一些细节问题

重复抛出事件

因为监听的是 scroll 事件,并且使用 delta 做了一点提前加载,因此可能会触发多次触底事件(距离底部48px, 30px, 12px... 都符合判断条件),造成页面重复多次加载数据。因此选择在组件中增加一个 loadingMore 属性,表示父页面正在加载数据,此时不再继续抛出事件;当页面完成加载后,将 loadingMore 重置为 false 以继续监听触底。

没有更多数据

在信息流已经到底,没有更多数据的时候,触底事件就没有作用了。当然这个判断也可以在父页面中进行,当没有更多数据时不再继续处理触底事件;不过为了方便页面中的加载数据方法(不用每次都单独判断是否有更多),给组件增加一个 noMore 参数,此时不再抛出触底事件。

代码实现

bottomDetector 组件

<template>
  <div style="text-align: center">
    <div v-if="loadingMore">加载中</div>
    <div v-if="noMore">没有更多了</div>
  </div>
</template>
<script>
export default {
  props: {
    loadingMore: {
      type: Boolean,
      required: true,
    },
    distance: {
      type: Number,
      default: 50,
    },
    noMore: {
      type: Boolean,
      default: false,
    },
  },
  mounted() {
    window.addEventListener('scroll', this.listenBottomOut)
    this.element = document.documentElement
  },
  destroyed() {
    window.removeEventListener('scroll', this.listenBottomOut, false)
  },
  data() {
    return {
      element: null,
    }
  },
  methods: {
    listenBottomOut() {
      if (this.noMore || this.loadingMore) return
      let scrollTop = this.element.scrollTop || document.body.scrollTop
      let clientHeight = this.element.clientHeight
      let scrollHeight = this.element.scrollHeight
      if (scrollTop + clientHeight >= scrollHeight - this.distance) {
        this.$emit('arriveBottom')
      }
    },
  },
}
</script>

页面使用

从触底功能上来说,detector 组件放在页面中的任何位置都可以。放在信息流下方的话还可以顺便做加载动画和信息到底的提示信息。

<div class="infos">
  <div v-for="item in infos" :key="item.id">
    <!-- info -->
  </div>
</div>
<bottom-detector
  :loadingMore="infosLoadingMore"
  :noMore="infos.length >= infoTotal"
  @arriveBottom="getMoreInfo"
/>
</div>
getMoreInfo() {
  this.infosLoadingMore = true
  // loading more info
  this.infosLoadingMore = false
},

结语

以上是简单的 Vue 页面触底加载组件的原理及实现,希望对你有所帮助,欢迎在评论区进行讨论或指正。

监听事件函数部分参考了博客:触底加载更多(原理 + 在vue中的使用)

上一篇:element-select当下拉框数据过多使用懒加载


下一篇:vue + elememt ui table 实现滚屏效果