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

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

场景

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

实现

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

思路

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

代码

定义模板

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 就是这样)
  • 没有一键回到顶部的功能,毕竟翻了太久的话回到顶部很麻烦呢
  • 自定义属性还是不够,例如一页的数据的条数,最大页数什么的