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实现图片左右自动滚动示例
Sep 25 Javascript
浅谈JavaScript字符集
May 22 Javascript
Js与Jq 获取页面元素值的方法和差异对比
Apr 30 Javascript
jQuery实现的类似淘宝网站搜索框样式代码分享
Aug 24 Javascript
AngularJS入门教程之模块化操作用法示例
Nov 02 Javascript
使用纯JS代码判断字符串中有多少汉字的实现方法(超简单实用)
Nov 12 Javascript
利用prop-types第三方库对组件的props中的变量进行类型检测
May 02 Javascript
详解JS中的this、apply、call、bind(经典面试题)
Sep 19 Javascript
使用 electron 实现类似新版 QQ 的登录界面效果(阴影、背景动画、窗体3D翻转)
Oct 23 Javascript
ElementUI Tag组件实现多标签生成的方法示例
Jul 08 Javascript
基于Vue的侧边目录组件的实现
Feb 05 Javascript
详解三种方式在React中解决绑定this的作用域问题并传参
Aug 18 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中通过smtp发邮件的类,测试通过
2007/01/22 PHP
Yii学习总结之安装配置
2015/02/22 PHP
PHP简单实现断点续传下载的方法
2015/09/25 PHP
php使用curl模拟多线程实现批处理功能示例
2019/07/25 PHP
JavaScript中this关键字使用方法详解
2007/03/08 Javascript
IE与FireFox的兼容性问题分析
2007/04/22 Javascript
浅谈javascript中的作用域
2012/04/07 Javascript
浅析JavaScript中的常用算法与函数
2013/11/21 Javascript
Javascript实现简单二级下拉菜单实例
2014/06/15 Javascript
jquery增加和删除元素的方法
2015/01/14 Javascript
jscript读写二进制文件的方法
2015/04/22 Javascript
跟我学习javascript的循环
2015/11/18 Javascript
javascript轻量级库createjs使用Easel实现拖拽效果
2016/02/19 Javascript
玩转NODE.JS(四)-搭建简单的聊天室的代码
2016/11/11 Javascript
Vue异步组件使用详解
2017/04/08 Javascript
详解小程序云开发攻略(解决最棘手的问题)
2019/09/30 Javascript
vue项目创建步骤及路由router
2020/01/14 Javascript
js实现弹窗猜数字游戏
2020/11/26 Javascript
在实例中重学JavaScript事件循环
2020/12/03 Javascript
[02:04]2016国际邀请赛中国区预选赛VG.R晋级之路
2016/07/01 DOTA
python 字典(dict)按键和值排序
2016/06/28 Python
python中解析json格式文件的方法示例
2017/05/03 Python
Python 查看文件的读写权限方法
2018/01/23 Python
详解python中的线程
2018/02/10 Python
python+ffmpeg批量去视频开头的方法
2019/01/09 Python
python登录WeChat 实现自动回复实例详解
2019/05/28 Python
全天然狗零食:Best Bully Sticks
2016/09/22 全球购物
购买200个世界上最好的内衣品牌:Bare Necessities
2017/02/11 全球购物
一道输出判断型Java面试题
2014/10/01 面试题
求职推荐信范文
2013/12/01 职场文书
中国文明网签名寄语
2014/01/18 职场文书
毕业生银行实习自我鉴定
2014/10/14 职场文书
模范教师材料大全
2014/12/16 职场文书
写给父母的感谢信
2015/01/22 职场文书
自主招生推荐信怎么写
2015/03/26 职场文书
有关花店创业的计划书模板
2019/08/27 职场文书