Vue 实现一个简单的瀑布流组件
本文最后更新于:2020年12月30日 晚上
场景
在用 Vue 写前端的时候,需要实现无限滚动翻页的功能。因为用到的地方很多,于是便想着抽出一个通用组件。
实现
思路
- 定义一个 vuejs 容器组件
- 抽离出公共的属性(加载一页数据的函数/每个元素的模板)
- 在父容器中遍历每个元素并绑定到传入的模板上
- 监听滚动事件,如果不是最后一页就加载下一页
- 重新渲染集合元素
代码
定义模板
/**
自定义瀑布流组件
使用方法如下:
<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 就是这样)
- 没有一键回到顶部的功能,毕竟翻了太久的话回到顶部很麻烦呢
- 自定义属性还是不够,例如一页的数据的条数,最大页数什么的
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!