本文最后更新于:2020年12月30日 中午
场景
在用 Vue 写前端的时候,需要实现无限滚动翻页的功能。因为用到的地方很多,于是便想着抽出一个通用组件。
实现
实现源码放到了 GitHub,Demo 演示 想直接看源码/效果的人可以直接去看
思路
- 定义一个 vuejs 容器组件
- 抽离出公共的属性(加载一页数据的函数/每个元素的模板)
- 在父容器中遍历每个元素并绑定到传入的模板上
- 监听滚动事件,如果不是最后一页就加载下一页
- 重新渲染集合元素
代码
定义模板
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| /** 自定义瀑布流组件 使用方法如下: <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>
|
使用
使用起来就很简单了
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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| <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 就是这样)
- 没有一键回到顶部的功能,毕竟翻了太久的话回到顶部很麻烦呢
- 自定义属性还是不够,例如一页的数据的条数,最大页数什么的