vue实现瀑布流组件滑动加载更多

作者:BenjaminShih 时间:2024-05-02 17:09:45 

建议先看vue瀑布流组件上拉加载更多再来食用本文,如果直接想看源码文末就是~

文末新增组件优化,之所以没有删优化前的代码是想让以后自己还能看到走过的路。

上一篇讲到在项目中使用上拉加载更多组件,但是由于实际项目开发中由于需求变更或者说在webview中上拉加载有些机型在上拉时候会把webview也一起上拉导致上拉加载不灵敏等问题,我们有时候也会换成滑动到底部自动加载的功能。

既然都是加载更多,很多代码思想势必相似,主要区别在于上拉和滑动到底部这个操作上,所以,我们需要注意:

1、上拉加载是point指针touch触摸事件,现在因为是滑动加载,需要添加scroll事件去监听然后执行相应回调
2、上拉加载主要计算触摸滚动距离,滑动加载主要计算container底部和视窗上边缘的距离

事件绑定改成:


mounted() {
···
this.dom.addEventListener('scroll', this.scroll, false)
···
},

beforeDestroy() {
···
this.dom.removeEventListener('scroll', this.scroll, false)
···
},

事件回调改为:


/**
* 滚动钩子
*/
scroll() {
const viewHeight = global.innerHeight
let parentNode
if (this.container !== global) {
 parentNode = this.$el
} else {
 parentNode = this.$el.parentNode
}
if (parentNode) {
 // 获取Vue实例使用的根 DOM 元素相对于视口的位置
 const rect = parentNode.getBoundingClientRect()
 // this.distance 离底部多少距离开始加载
 // 如果此元素底边距离视口顶部的距离小于视口高度加上distance之和,就加载下一页
 if ((rect.bottom <= viewHeight + this.distance) && this.loadable && !this.loading) {
 this.load()
 }
}
},

源码如下:


<template>
<div class="loadmore" ref="loadmore">
<div class="loadmore__body">
<slot></slot>
</div>
<div class="loadmore__footer">
<span v-if="loading">
<i class="tc-loading"></i>
<span>正在加载</span>
</span>
<span v-else-if="loadable">加载更多</span>
<span v-else>没有更多了</span>
</div>
</div>
</template>

<script type="text/babel">
import axios from 'axios'

const CancelToken = axios.CancelToken

export default {
data() {
return {
/**
 * 总页数(由服务端返回)
 * @type {number}
 */
count: 0,

/**
 * 是否正在拖拽中
 * @type {boolean}
 */
dragging: false,

/**
 * 已加载次数
 * @type {number}
 */
times: 0,

/**
 * 已开始记载
 * @type {boolean}
 */
started: false,

/**
 * 正在加载中
 * @type {boolean}
 */
loading: false,

dom: null,
}
},

props: {
/**
* 初始化后自动开始加载数据
*/
autoload: {
type: Boolean,
default: true,
},

/**
* 离组件最近的可滚动父级元素(用于监听事件及获取滚动条位置)
*/
container: {
// Selector or Element
default: () => (global),
},

/**
* Axios请求参数配置对象
* {@link https://github.com/mzabriskie/axios#request-config}
*/
options: {
type: Object,
default: null,
},

/**
* 起始页码
*/
page: {
type: Number,
default: 1,
},

/**
* 每页加载数据条数
*/
rows: {
type: Number,
default: 10,
},

/**
* 数据加载请求地址
*/
url: {
type: String,
default: '',
},

/**
* 距离底部多远加载
*/
distance: {
type: Number,
default: 200,
},
},

computed: {
/**
* 是否可以加载
* @returns {boolean} 是与否
*/
loadable() {
return !this.started || (this.page + this.times) <= this.count
},
},

mounted() {
if (this.container !== global) {
this.dom = document.querySelector(this.container)
} else {
this.dom = this.container
}
if (!this.dom) {
return
}
this.dom.addEventListener('scroll', this.scroll, false)
if (this.autoload && !this.loading) {
this.load()
}
},

// eslint-disable-next-line
beforeDestroy() {
if (this.dom) {
this.dom.removeEventListener('scroll', this.scroll, false)
}
},

methods: {
/**
* 滚动钩子
*/
scroll() {
const viewHeight = global.innerHeight
let parentNode
if (this.container !== global) {
 parentNode = this.$el
} else {
 parentNode = this.$el.parentNode
}
if (parentNode) {
 const rect = parentNode.getBoundingClientRect()
 if ((rect.bottom <= viewHeight + this.distance) && this.loadable && !this.loading) {
 this.load()
 }
}
},
/**
* 加载一组数据的方法
*/
load() {
if (this.loading) {
 return
}
this.started = true
this.loading = true

const params = {
 currentPage: this.page + this.times,
 pageSize: this.rows,
}
const options = Object.assign({}, this.options, {
 url: this.url,
 cancelToken: new CancelToken((cancel) => {
 this.cancel = cancel
 }),
})

if (String(options.method).toUpperCase() === 'POST') {
 options.data = Object.assign({}, options.data, params)
} else {
 options.params = Object.assign({}, options.params, params)
}

this.$axios.request(options).then((res) => {
 const data = res.result
 this.times += 1
 this.loading = false
 this.count = data.pageCount
 this.$emit('success', data.list)
 this.$emit('complete')
}).catch((e) => {
 this.loading = false
 this.$emit('error', e)
 this.$emit('complete')
})
},

/**
* 重置加载相关变量
*/
reset() {
this.count = 0
this.times = 0
this.started = false
this.loading = false
},

/**
*重新开始加载
*/
restart() {
this.reset()
this.load()
},
},
}
</script>

———————-我是分割线——————–

2017-09-18 组件优化

我们在写组件时候,通常会大致先分为两种,业务组件和通用组件,业务组件通和业务逻辑相关,一般作为一个业务模块的局部组件, 比如列表中的列表项组件;通用组件适用面广,不会和业务有牵扯,比如弹出框组件。

所以我们开始封装一个组件的时候,就要划分业务逻辑,做什么,不做什么,从外部接收什么,向外部提供什么,这个边界应该非常清楚

但是之前的封装的loadmore组件不太符合这一点,可能是项目一开始比较关注功能的实现,将其当成的一个业务组件撰写,现在有一点需要优化:

之前我们传入了各种请求相关的参数,包括url在组件内部完成加载和页码控制等一系列操作,显然这不太符合组件功能职责单一化的原则, 其实组件内部并不关心加载到第几页或者是需要请求什么后端接口,而只要父组件告诉自己是否还可以加载就可以了, 至于加载请求列表,子组件通知父组件去加载就OK。

最终我们得到一个和业务完全分离的通用组件,代码如下:


<template>
<div class="loadmore" ref="loadmore">
<div class="loadmore__body">
<slot></slot>
</div>
<div class="loadmore__footer">
<span v-if="loading">
<i class="tc-loading"></i>
<span>正在加载</span>
</span>
<span v-else-if="loading">正在加载...</span>
<span v-else>没有更多了</span>
</div>
</div>
</template>

<script>
export default {
props: {
/**
* 是否加载
*/
loading: {
type: Boolean,
default: false,
},

/**
* 滚动外部容器, 选择器字符串
*/
container: {
default: () => (global),
},

/**
* 距离底部多远加载
*/
distance: {
type: Number,
default: 200,
},
},

data() {
return {
dom: null, // 外部容器dom
}
},
mounted() {
if (this.container !== global) {
this.dom = document.querySelector(this.container)
} else {
this.dom = this.container
}
if (!this.dom) {
return
}
this.dom.addEventListener('scroll', this.scroll, false)
if (this.autoload && !this.loading) {
this.load()
}
},

methods: {
/**
* 滚动钩子
*/
scroll() {
if (!this.loading) {
return
}
const viewHeight = global.innerHeight
let parentNode
if (this.container !== global) {
parentNode = this.$el
} else {
parentNode = this.$el.parentNode
}
if (parentNode) {
const rect = parentNode.getBoundingClientRect()
if ((rect.bottom <= viewHeight + this.distance)) {
 this.load()
}
}
},
/**
* 加载一组数据的方法
*/
load() {
this.$emit('load')
},
},
beforeDestroy() {
if (this.dom) {
this.dom.removeEventListener('scroll', this.scroll, false)
}
},
}
</script>
<style lang="stylus" rel="stylesheet/stylus" scoped>
.loadmore {
user-select: none;

&__footer {
color: #e4e4e4;
padding: 20px;
text-align: center;
}

.tc-loading {
~ span {
vertical-align: middle;
}
}
}
</style>

来源:https://blog.csdn.net/sjn0503/article/details/75314641

标签:vue,瀑布流,加载
0
投稿

猜你喜欢

  • 完美解决ARIMA模型中plot_acf画不出图的问题

    2023-07-13 14:17:34
  • Mysql出生日期转换为年龄并分组统计人数的方法示例

    2024-01-14 14:58:03
  • Vue 3.0的attribute强制行为理解学习

    2024-05-05 09:22:25
  • Golang设计模式工厂模式实战写法示例详解

    2024-05-25 15:11:42
  • Anaconda修改默认虚拟环境安装位置的方案分享

    2022-11-19 22:36:52
  • flask+layui+echarts实现前端动态图展示数据效果

    2023-06-24 15:41:55
  • Anaconda使用IDLE的实现示例

    2023-05-09 10:40:39
  • Django通过json格式收集主机信息

    2022-03-23 19:58:49
  • Ubuntu 18.04配置mysql以及配置远程连接的步骤

    2024-01-14 14:54:39
  • keras模型可视化,层可视化及kernel可视化实例

    2021-02-20 00:45:25
  • vue-cli中使用高德地图的方法示例

    2024-04-27 16:09:39
  • go语言实现猜数字小游戏的方法

    2024-02-12 11:47:18
  • PHP扩展Swoole实现实时异步任务队列示例

    2023-11-10 05:11:22
  • mybatis plus实体类中字段映射mysql中的json格式方式

    2024-01-22 16:11:37
  • python之关于数组和列表的区别及说明

    2021-07-29 17:35:31
  • Python中gevent模块协程使用

    2023-10-23 02:56:46
  • MySQL 不允许从远程访问的解决方法

    2024-01-27 03:42:25
  • 详解Python中的多线程编程

    2023-09-17 00:34:08
  • Python for循环通过序列索引迭代过程解析

    2021-11-30 05:36:50
  • Python实现的使用telnet登陆聊天室实例

    2023-11-17 10:41:17
  • asp之家 网络编程 m.aspxhome.com