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


Posted in Javascript onMarch 10, 2020

建议先看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>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
12个非常有创意的JavaScript小游戏
Mar 18 Javascript
zShowBox 图片放大展示jquery版 兼容性
Sep 24 Javascript
用模版生成HTML的的框架jquery.tmpl使用详解
Jan 07 Javascript
JavaScript的Backbone.js框架的一些使用建议整理
Feb 14 Javascript
jQuery实现的可编辑表格完整实例
Jun 20 Javascript
快速将Vue项目升级到webpack3的方法步骤
Sep 14 Javascript
Vue隐藏显示、只读实例代码
Jul 18 Javascript
Vue.js更改调试地址端口号的实例
Sep 19 Javascript
JavaScript遍历数组的三种方法map、forEach与filter实例详解
Feb 27 Javascript
js获取form表单中name属性的值
Feb 27 Javascript
微信小程序-form表单提交代码实例
Apr 29 Javascript
Vue项目配置跨域访问和代理proxy设置方式
Sep 08 Javascript
JavaScript代理模式原理与用法实例详解
Mar 10 #Javascript
JavaScript命令模式原理与用法实例详解
Mar 10 #Javascript
JavaScript实现指定数量的并发限制的示例代码
Mar 10 #Javascript
vue中的使用token的方法示例
Mar 10 #Javascript
vue瀑布流组件实现上拉加载更多
Mar 10 #Javascript
JS如何在数组指定位置插入元素
Mar 10 #Javascript
vue实现简单瀑布流布局
May 28 #Javascript
You might like
删除无限级目录与文件代码共享
2006/07/12 PHP
php 设计模式之 单例模式
2008/12/19 PHP
隐性调用php程序的方法
2009/03/09 PHP
php-redis中的sort排序函数总结
2015/07/08 PHP
Javascript的一种模块模式
2008/03/22 Javascript
jQuery 定时局部刷新(setInterval)
2010/11/19 Javascript
JQuery记住用户名和密码的具体实现
2014/04/04 Javascript
node.js中的buffer.Buffer.isBuffer方法使用说明
2014/12/14 Javascript
JavaScript判断是否为数组的3种方法及效率比较
2015/04/01 Javascript
vue插件tab选项卡使用小结
2016/10/27 Javascript
Vue.js实战之利用vue-router实现跳转页面
2017/04/01 Javascript
详解nodejs微信公众号开发——6.自定义菜单
2017/04/13 NodeJs
详解puppeteer使用代理
2018/12/27 Javascript
微信小程序学习笔记之登录API与获取用户信息操作图文详解
2019/03/29 Javascript
浅谈layui框架自带分页和表格重载的接口解析问题
2019/09/11 Javascript
原生js实现点击轮播切换图片
2020/02/11 Javascript
解决qrcode.js生成二维码时必须定义一个空div的问题
2020/07/09 Javascript
VUE异步更新DOM - 用$nextTick解决DOM视图的问题
2020/11/06 Javascript
python实现数据写入excel表格
2018/03/25 Python
基于python进行桶排序与基数排序的总结
2018/05/29 Python
Python 确定多项式拟合/回归的阶数实例
2018/12/29 Python
Python中一些深不见底的“坑”
2019/06/12 Python
python如何获取列表中每个元素的下标位置
2019/07/01 Python
用Python做一个久坐提醒小助手的示例代码
2020/02/10 Python
python GUI库图形界面开发之PyQt5布局控件QVBoxLayout详细使用方法与实例
2020/03/06 Python
python:删除离群值操作(每一行为一类数据)
2020/06/08 Python
python对批量WAV音频进行等长分割的方法实现
2020/09/25 Python
python 两种方法删除空文件夹
2020/09/29 Python
Python中的流程控制详解
2021/02/18 Python
澳大利亚女性快速时尚零售商:Ally Fashion
2018/04/25 全球购物
程序员岗位职责
2013/11/11 职场文书
大学生文员专业个人求职信范文
2014/01/05 职场文书
2014法院干警廉洁警示教育思想汇报
2014/09/13 职场文书
员工自我工作评价
2015/03/06 职场文书
离婚被告答辩状
2015/05/22 职场文书
css布局巧妙技巧之css三角示例的运用
2022/03/16 HTML / CSS