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 入门实例1
Jun 25 Javascript
JS实现的一个简单的Autocomplete自动完成例子
Apr 16 Javascript
jQuery Validate初步体验(二)
Dec 12 Javascript
Bootstrap Table使用心得总结
Nov 29 Javascript
Bootstrap显示与隐藏简单实现代码
Mar 06 Javascript
浅谈JavaScript正则表达式-非捕获性分组
Mar 08 Javascript
vue多次循环操作示例
Feb 08 Javascript
react中使用css的7中方式(最全总结)
Feb 11 Javascript
微信小程序可滑动月日历组件使用详解
Oct 21 Javascript
koa-passport实现本地验证的方法示例
Feb 20 Javascript
maptalks+three.js+vue webpack实现二维地图上贴三维模型操作
Aug 10 Javascript
JS绘图Flot如何实现可选显示曲线图功能
Oct 16 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
第六节 访问属性和方法 [6]
2006/10/09 PHP
PHP多线程类及用法实例
2014/12/03 PHP
用jQuery实现检测浏览器及版本的脚本代码
2008/01/22 Javascript
jQuery中RadioButtonList的功能及用法实例介绍
2013/08/23 Javascript
js鼠标滑轮滚动事件绑定的简单实例(兼容主流浏览器)
2014/01/14 Javascript
javascript实现网页字符定位的方法
2015/07/14 Javascript
全面了解函数声明与函数表达式、变量提升
2016/08/09 Javascript
AngularJS路由Ui-router模块用法示例
2017/05/29 Javascript
Angular Renderer (渲染器)的具体使用
2018/05/03 Javascript
Vue 实现列表动态添加和删除的两种方法小结
2018/09/07 Javascript
vue单页面应用打开新窗口显示跳转页面的实例
2018/09/21 Javascript
详解js删除数组中的指定元素
2018/10/31 Javascript
微信小程序生成分享海报方法(附带二维码生成)
2019/03/29 Javascript
JavaScript实现抖音罗盘时钟
2019/10/11 Javascript
jquery实现直播视频弹幕效果
2020/02/25 jQuery
angular组件间传值测试的方法详解
2020/05/07 Javascript
JavaScript异步操作的几种常见处理方法实例总结
2020/05/11 Javascript
[01:34]2014DOTA2 TI预选赛预选赛 选手比赛房大揭秘!
2014/05/20 DOTA
Python爬虫包BeautifulSoup异常处理(二)
2018/06/17 Python
python3+pyqt5+itchat微信定时发送消息的方法
2019/02/20 Python
Python实现的北京积分落户数据分析示例
2020/03/27 Python
Python定义一个Actor任务
2020/07/29 Python
Python使用Turtle模块绘制国旗的方法示例
2021/02/28 Python
用HTML5制作视频拼图的教程
2015/05/13 HTML / CSS
德国狗狗用品在线商店:Schecker
2017/03/17 全球购物
意大利奢侈品购物网站:Giglio
2018/01/05 全球购物
哪些情况下不应该使用索引
2015/07/20 面试题
十岁生日家长答谢词
2014/01/17 职场文书
公务员平时考核实施方案
2014/03/11 职场文书
百日安全生产活动总结
2014/07/05 职场文书
员工教育培训协议书
2014/09/27 职场文书
先进个人事迹材料
2014/12/29 职场文书
2015年社区科普工作总结
2015/05/13 职场文书
幼儿园秋季开学通知
2015/07/16 职场文书
通讯稿范文
2015/07/22 职场文书
Linux磁盘管理方法介绍
2022/06/01 Servers