Vue 实现一个简单的瀑布流组件

本文最后更新于:2020年12月30日 晚上

场景

在用 Vue 写前端的时候,需要实现无限滚动翻页的功能。因为用到的地方很多,于是便想着抽出一个通用组件。

实现

实现源码放到了 GitHubDemo 演示 想直接看源码/效果的人可以直接去看

思路

  1. 定义一个 vuejs 容器组件
  2. 抽离出公共的属性(加载一页数据的函数/每个元素的模板)
  3. 在父容器中遍历每个元素并绑定到传入的模板上
  4. 监听滚动事件,如果不是最后一页就加载下一页
  5. 重新渲染集合元素

代码

定义模板

/**
自定义瀑布流组件
使用方法如下:
<rx-waterfalls-flow :load="load">
    <!-- 这里 slotProps 绑定的便是子组件的数据,通过 slotProps 可以访问到子组件绑定到模板上的数据,当然,更简单的方法是使用 ES6 的解构 -->
    <template slot-scope="{item}">
  <!-- 在模板里面便可以使用集合中的元素 item 了 -->
  <li :key="item.id">
    {{item.text}}
  </li>
</template>
  </rx-waterfalls-flow>
 */
<template>
  <div id="rx-waterfalls-flow-container">
    <slot
      v-for="item in items"
      :item="item"
    />
  </div>
</template>

<script>
export default {
  props: {
    load: {
      type: Function,
      default: function () {
        throw new Error('你需要为 RxWaterfallsFlow 组件定义分页加载的参数')
      }
    }
  },
  data: () => ({
    items: [],
    page: {
      total: 0,
      size: 10,
      pages: 10,
      current: 1,
      records: []
    }
  }),
  methods: {
    async loadPage (current, size) {
      this.page = await this.load(current, size)
      this.items.push(...this.page.records)
      this.page.records = []
    },
    /**
     * 初始化方法,加载第一页的数据,加载监听器
     */
    async init () {
      this.loadPage()
      // 绑定窗口滚动事件
      // 获得文档高度和滚动高度
      // 计算是否已经到底了
      // 到底的话就加载下一页的数据,否则忽略
      const otherOnscrollFn = document.onscroll ? document.onscroll : function () { }
      document.onscroll = () => {
        otherOnscrollFn()
        const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
        const clientHeight = document.documentElement.clientHeight
        const scrollHeight = document.documentElement.scrollHeight
        // console.log(`已滚动的高度:${scrollTop}, 滚动条高度:${scrollHeight}, ${clientHeight}`)
        // 向下滚动时判断判断是否正在向上滚动,是的话就清除定时器,停在当前位置
        if (scrollHeight - scrollTop - clientHeight <= 0 && this.page.current < this.page.pages) {
          this.loadPage(this.page.current + 1, this.page.size)
        }
      }
    }
  },
  mounted () {
    this.init()
  }
}
</script>

<style scoped>
/* 容器宽度要占 100% */
#rx-waterfalls-flow-container {
  width: 100%;
}
</style>

使用

使用起来就很简单了

<template>
  <rx-waterfalls-flow :load="load">
    <!-- 这里 slotProps 绑定的便是子组件的数据,通过 slotProps 可以访问到子组件绑定到模板上的数据,当然,更简单的方法是使用 ES6 的解构 -->
    <!-- 这里面的是你自定义每个元素显示的内容 -->
    <template slot-scope="{item}">
      <!-- 在模板里面便可以使用集合中的元素 item 了 -->
      <li
        class="item-style"
        :key="item.id"
      >
        {{item.text}}
      </li>
    </template>
  </rx-waterfalls-flow>
</template>

<script>
// 引入瀑布流组件
import RxWaterfallsFlow from '@/components/common/RxWaterfallsFlow'
import _ from 'lodash'

export default {
  components: {
    RxWaterfallsFlow
  },
  methods: {
    // 使用 Promise 封装 setTimeout,模拟 ajax 的异步造成的延迟
    await: ms => new Promise(resolve => setTimeout(resolve, ms)),
    load: (page => {
      // 该方法用于模拟 ajax 数据加载
      return async function () {
        await this.await(1000)
        console.log(`加载了第 ${page.current} 页,共 ${page.pages} 页`)
        // 使用 lodash 模拟数据
        page.records = _.range(
          (page.current - 1) * page.size + 1,
          (++page.current - 1) * page.size + 1
        )
          .map(i => ({
            id: i,
            text: `第 ${i} 行内容`
          }))
        return page
      }
    })({
      current: 1,
      size: 10,
      pages: 100,
      total: this.current * this.pages,
      records: []
    })
  }
}
</script>

<style>
li {
  width: 500px;
  height: 200px;
  line-height: 200px;
  background-color: aqua;
  margin: 10px auto;
}
</style>

缺陷

目前这个简单的瀑布流公用组件还有着相当多的缺陷,却是要等到后面再进行改进了呢

  • 没有 DOM 回收机制,会造成 DOM 树越来越大,网页就会变得越来越卡(Twitter 就是这样)
  • 没有一键回到顶部的功能,毕竟翻了太久的话回到顶部很麻烦呢
  • 自定义属性还是不够,例如一页的数据的条数,最大页数什么的

Vue 实现一个简单的瀑布流组件
https://blog.rxliuli.com/p/8fe300cfeedb4f40a5059c458c95e972/
作者
rxliuli
发布于
2020年2月2日
许可协议