vue瀑布流组件实现上拉加载更多


Posted in Javascript onMarch 10, 2020

最近在做移动端h5页面,所以分页什么的就不能按照传统pc端的分页器的思维去做了,这么小的屏幕去点击也不太方便一般来讲移动端都是上拉加载更多,符合正常使用习惯。

首先简单写一下模板部分的html代码,,很简单清晰的逻辑:

<template>
 <div class="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>

然后就是业务部分了

在动手写组件之前,先理清需求:

加载页面 -> 滑到底部 -> 上拉一定距离 -> 加载第二页 -> 继续前面步骤 -> 没有更多

这是一个用户交互逻辑,而我们需要将其映射为代码逻辑:

首屏自动加载第一页 -> 滑动到底部&&按下时候滑动距离Y轴有一定偏移量 -> 请求后端加载第二页 -> 根据返回字段判断是否还有下一页

有了代码逻辑,主干就出来了,加载和判断由事件来控制,而又作为一个vue组件,我们需要配合vue生命周期来挂载事件和销毁事件

export default {
 mounted() {
  // 确定容器
  // 容器绑定事件
 },
 beforeDestory() {
  // 解绑事件
 },
}

如果没有解绑的话,每次你加载组件,就会绑定一次事件…

然后我们需要一些核心事件回调方法来在合适的时间加载数据渲染页面, 回想一下,第一我们需要http获取数据的load函数,然后我们需要三个绑定事件的回调函数pointDown(), pointMove(), pointUp(),分别对应用户按下、移动、弹起手指操作:

export default {
 ···
 methods:{
  /**
  * 加载一组数据的方法
  */
  load() {
   // 设置options
  this.$axios.request(options).then((res) => {
   // 获取数据后的处理
  }).catch((e) => {
   // 异常处理
  })
  },
  /**
  * 鼠标按下事件处理函数
  * @param {Object} e - 事件对象
  */
  pointerdown(e) {
  // 获取按下的位置
  this.pageY = e.changedTouches ? e.changedTouches[0].pageY : e.pageY
  },
  /**
  * 鼠标移动事件处理函数
  * @param {Object} e - 事件对象
  */
  pointermove(e) {
  const container = this.$container
  const pageY = e.changedTouches ? e.changedTouches[0].pageY : e.pageY
  const moveY = pageY - this.pageY

  // 如果已经向下滚动到页面最底部
  if (moveY < 0 && (container.scrollTop + Math.min(
   global.innerHeight,
   container.clientHeight,
  )) >= container.scrollHeight) {
   // 阻止原生的上拉拖动会露出页面底部空白区域的行为(主要针对iOS版微信)
   e.preventDefault()

   // 如果上拉距离超过50像素,则加载下一页
   if (moveY < -50) {
   this.pageY = pageY
   this.load()
   }
  }
  },
  /**
  * 鼠标松开事件处理函数
  */
  pointerup() {
  // 这边就是取消拖动状态,需要注意在拖动过程中不要再次触发一些事件回调,否侧乱套
  this.dragging = false
  },
 },
 ···
}

基本上主干已经算完工了,一些props传入或者一些逻辑控制细节需要再额外添加,贴出整个组件的源码:

<template>
 <div class="loadmore">
 <!-- <div class="loadmore__header"></div> -->
 <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,
  }
 },

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

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

  /**
  * 禁用组件
  */
  disabled: {
  type: Boolean,
  default: false,
  },

  /**
  * 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: '',
  },
 },

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

 mounted() {
  let container = this.container

  if (container) {
  if (typeof container === 'string') {
   container = document.querySelector(container)
  } else if (!container.querySelector) {
   container = document.body
  }
  }

  if (!container) {
  container = document.body
  }

  this.$container = container
  this.onPointerDown = this.pointerdown.bind(this)
  this.onPointerMove = this.pointermove.bind(this)
  this.onPointerUp = this.pointerup.bind(this)

  if (global.PointerEvent) {
  container.addEventListener('pointerdown', this.onPointerDown, false)
  container.addEventListener('pointermove', this.onPointerMove, false)
  container.addEventListener('pointerup', this.onPointerUp, false)
  container.addEventListener('pointercancel', this.onPointerUp, false)
  } else {
  container.addEventListener('touchstart', this.onPointerDown, false)
  container.addEventListener('touchmove', this.onPointerMove, false)
  container.addEventListener('touchend', this.onPointerUp, false)
  container.addEventListener('touchcancel', this.onPointerUp, false)
  container.addEventListener('mousedown', this.onPointerDown, false)
  container.addEventListener('mousemove', this.onPointerMove, false)
  container.addEventListener('mouseup', this.onPointerUp, false)
  }

  if (this.autoload) {
  this.load()
  }
 },

 // eslint-disable-next-line
 beforeDestroy() {
  const container = this.$container

  if (global.PointerEvent) {
  container.removeEventListener('pointerdown', this.onPointerDown, false)
  container.removeEventListener('pointermove', this.onPointerMove, false)
  container.removeEventListener('pointerup', this.onPointerUp, false)
  container.removeEventListener('pointercancel', this.onPointerUp, false)
  } else {
  container.removeEventListener('touchstart', this.onPointerDown, false)
  container.removeEventListener('touchmove', this.onPointerMove, false)
  container.removeEventListener('touchend', this.onPointerUp, false)
  container.removeEventListener('touchcancel', this.onPointerUp, false)
  container.removeEventListener('mousedown', this.onPointerDown, false)
  container.removeEventListener('mousemove', this.onPointerMove, false)
  container.removeEventListener('mouseup', this.onPointerUp, false)
  }

  if (this.loading && this.cancel) {
  this.cancel()
  }
 },

 methods: {
  /**
  * 加载一组数据的方法
  */
  load() {
  if (this.disabled || 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()
  },

  /**
  * 鼠标按下事件处理函数
  * @param {Object} e - 事件对象
  */
  pointerdown(e) {
  if (this.disabled || !this.loadable || this.loading) {
   return
  }

  this.dragging = true
  this.pageY = e.changedTouches ? e.changedTouches[0].pageY : e.pageY
  },

  /**
  * 鼠标移动事件处理函数
  * @param {Object} e - 事件对象
  */
  pointermove(e) {
  if (!this.dragging) {
   return
  }

  const container = this.$container
  const pageY = e.changedTouches ? e.changedTouches[0].pageY : e.pageY
  const moveY = pageY - this.pageY

  // 如果已经向下滚动到页面最底部
  if (moveY < 0 && (container.scrollTop + Math.min(
   global.innerHeight,
   container.clientHeight,
  )) >= container.scrollHeight) {
   // 阻止原生的上拉拖动会露出页面底部空白区域的行为(主要针对iOS版微信)
   e.preventDefault()

   // 如果上拉距离超过50像素,则加载下一页
   if (moveY < -50) {
   this.pageY = pageY
   this.load()
   }
  }
  },

  /**
  * 鼠标松开事件处理函数
  */
  pointerup() {
  this.dragging = false
  },
 },
 }
</script>

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

Javascript 相关文章推荐
jQuery Validation插件remote验证方式的Bug解决
Jul 01 Javascript
jcarousellite.js 基于Jquery的图片无缝滚动插件
Dec 30 Javascript
JQuery入门——用bind方法绑定事件处理函数应用介绍
Feb 05 Javascript
javascript闭包的高级使用方法实例
Jul 04 Javascript
BootStrap+Angularjs+NgDialog实现模式对话框
Aug 24 Javascript
分享一个原生的JavaScript拖动方法
Sep 25 Javascript
javascript实现日期三级联动下拉框选择菜单
Dec 03 Javascript
vue两个组件间值的传递或修改方式
Jul 04 Javascript
Vue CLI3 开启gzip压缩文件的方式
Sep 30 Javascript
微信小程序制作表格的方法
Feb 14 Javascript
微信小程序如何再次获取用户授权的方法
May 10 Javascript
vue实现简易图片左右旋转,上一张,下一张组件案例
Jul 31 Javascript
JS如何在数组指定位置插入元素
Mar 10 #Javascript
vue实现简单瀑布流布局
May 28 #Javascript
JavaScript观察者模式原理与用法实例详解
Mar 10 #Javascript
微信小程序用canvas画图并分享
Mar 09 #Javascript
JavaScript实现简单贪吃蛇效果
Mar 09 #Javascript
原生js无缝轮播插件使用详解
Mar 09 #Javascript
微信小程序自定义弹出模态框禁止底部滚动功能
Mar 09 #Javascript
You might like
PHP实现无限级分类(不使用递归)
2015/10/22 PHP
php array_multisort 对数组进行排序详解及实例代码
2016/10/27 PHP
js判断浏览器的比较全的代码
2007/02/13 Javascript
JS 巧妙获取剪贴板数据 Excel数据的粘贴
2009/07/09 Javascript
高效率JavaScript编写技巧整理
2013/08/23 Javascript
jQuery+HTML5美女瀑布流布局实现方法
2015/09/21 Javascript
JavaScript中循环遍历Array与Map的方法小结
2016/03/12 Javascript
AngularJS在IE8的不支持的解决方法
2016/05/13 Javascript
30分钟快速掌握Bootstrap框架
2016/05/24 Javascript
使用Javascript判断浏览器终端设备(PC、IOS(iphone)、Android)
2017/01/04 Javascript
利用VUE框架,实现列表分页功能示例代码
2017/01/12 Javascript
canvas仿iwatch时钟效果
2017/03/06 Javascript
微信小程序实现图片轮播及文件上传
2017/04/07 Javascript
Vue 实现拖动滑块验证功能(只有css+js没有后台验证步骤)
2018/08/24 Javascript
在vue项目中引入vue-beauty操作方法
2019/02/11 Javascript
微信自定义分享链接信息(标题,图片和内容)实现过程详解
2019/09/04 Javascript
js blob类型url的视频下载问题的解决
2019/11/29 Javascript
[03:55]显微镜下的DOTA2特别篇——430灰烬之灵神级操作
2014/06/24 DOTA
Python脚本获取操作系统版本信息
2016/12/17 Python
完美解决python中ndarray 默认用科学计数法显示的问题
2018/07/14 Python
python解压TAR文件至指定文件夹的实例
2019/06/10 Python
使用Flask-Cache缓存实现给Flask提速的方法详解
2019/06/11 Python
Python函数默认参数常见问题及解决方案
2020/03/26 Python
Selenium向iframe富文本框输入内容过程图解
2020/04/10 Python
python3.6中anaconda安装sklearn踩坑实录
2020/07/28 Python
Python __slots__的使用方法
2020/11/15 Python
美国马匹用品和骑马配件购物网站:Horse.com
2018/01/08 全球购物
卡骆驰新加坡官网:Crocs新加坡
2018/06/12 全球购物
嘻哈珠宝品牌:KRKC&CO
2020/10/19 全球购物
电气自动化自荐信
2013/10/10 职场文书
小学科学教学反思
2014/01/26 职场文书
幼儿园母亲节活动方案
2014/03/10 职场文书
2014年专项整治工作总结
2014/11/17 职场文书
2014年销售部工作总结
2014/12/01 职场文书
乡镇安全生产月活动总结
2015/05/08 职场文书
python3读取文件指定行的三种方法
2021/05/24 Python