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 相关文章推荐
枚举JavaScript对象的函数
Dec 22 Javascript
服务端 VBScript 与 JScript 几个相同特性的写法 By shawl.qiu
Mar 06 Javascript
JavaScript 组件之旅(二)编码实现和算法
Oct 28 Javascript
js使下拉列表框可编辑不止是选择
Dec 12 Javascript
js函数参数设置默认值的一种变通实现方法
May 26 Javascript
轻松搞定jQuery.noConflict()
Feb 15 Javascript
深入了解JavaScript的逻辑运算符(与、或)
Dec 20 Javascript
codeMirror插件使用讲解
Jan 16 Javascript
JavaScript判断浏览器和hack滚动条的写法
Jul 23 Javascript
关于Google发布的JavaScript代码规范你要知道哪些
Apr 04 Javascript
vue使用ElementUI时导航栏默认展开功能的实现
Jul 04 Javascript
在Vue中创建可重用的 Transition的方法
Jun 02 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实现像JSP,ASP里Application那样的全局变量
2007/01/12 PHP
获取PHP警告错误信息的解决方法
2013/06/03 PHP
PHP中类的继承和用法实例分析
2016/05/24 PHP
laravel框架select2多选插件初始化默认选中项操作示例
2020/02/18 PHP
通用JS事件写法实现代码
2009/01/07 Javascript
仅Firefox中链接A无法实现模拟点击以触发其默认行为
2011/07/31 Javascript
javascript数字格式化通用类 accounting.js使用
2012/08/24 Javascript
用JQuery实现全选与取消的两种简单方法
2014/02/22 Javascript
js获取url中的参数且参数为中文时通过js解码
2014/03/19 Javascript
js转化毫秒为时间格式代码
2014/04/10 Javascript
浅谈下拉菜单中的Option对象
2015/05/10 Javascript
JavaScript设计模式初探
2016/01/07 Javascript
javascript HTML5 canvas实现打砖块游戏
2020/06/18 Javascript
JavaScript利用Date实现简单的倒计时实例
2017/01/12 Javascript
jQuery中.attr()和.data()的区别分析
2017/09/03 jQuery
NodeJs实现定时任务的示例代码
2017/12/05 NodeJs
JS实现移动端触屏拖拽功能
2018/07/31 Javascript
解决Vue2.0中使用less给元素添加背景图片出现的问题
2018/09/03 Javascript
详解vue-cli 2.0配置文件(小结)
2019/01/14 Javascript
运用js实现图层拖拽的功能
2019/05/24 Javascript
使用vue for时为什么要key【推荐】
2019/07/11 Javascript
[48:56]2018DOTA2亚洲邀请赛 3.31 小组赛 A组 VG vs KG
2018/03/31 DOTA
合并Excel工作薄中成绩表的VBA代码,非常适合教育一线的朋友
2009/04/09 Python
Python HTTP客户端自定义Cookie实现实例
2017/04/28 Python
tensorflow构建BP神经网络的方法
2018/03/12 Python
通过Python模块filecmp 对文件比较的实现方法
2018/06/29 Python
Django-celery-beat动态添加周期性任务实现过程解析
2020/11/26 Python
CSS的background属性及CSS3的背景图片设置总结
2016/06/13 HTML / CSS
Etam俄罗斯:法国女士内衣和家居服网上商店
2019/10/30 全球购物
SQL Server笔试题
2012/01/10 面试题
小学生获奖感言范文
2014/02/02 职场文书
《曹刿论战》教学反思
2014/03/02 职场文书
个人学习党的群众路线教育实践活动心得体会
2014/11/05 职场文书
2015元旦联欢晚会结束语
2014/12/14 职场文书
草系十大最强宝可梦,纸片人上榜,榜首大家最熟悉
2022/03/18 日漫
Vue OpenLayer 为地图绘制风场效果
2022/04/24 Vue.js