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

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

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

(0)

相关推荐

  • vue2.0 移动端实现下拉刷新和上拉加载更多的示例

    本人正在基于 vue2.0 + webpack + es6 搭建前端架构,整理了部分插件,下面这个是下拉更新 上拉更多的,挺好用的,分享给大家. 直接上代码,不懂的多看几遍,下面我换会告诉大家如何使用. <template lang="html"> <div class="yo-scroll" :class="{'down':(state===0),'up':(state==1),refresh:(state===2),touch:tou

  • vue实现网络图片瀑布流 + 下拉刷新 + 上拉加载更多(步骤详解)

    一.思路分析和效果图 用vue来实现一个瀑布流效果,加载网络图片,同时有下拉刷新和上拉加载更多功能效果.然后针对这几个效果的实现,捋下思路: 根据加载数据的顺序,依次追加标签展示效果: 选择哪种方式实现瀑布流,这里选择绝对定位方式: 关键问题:由于每张图片的宽高不一样,而瀑布流中要求所有图片的宽度一致,高度随宽度等比缩放.而且由于图片的加载是异步延迟.在不知道图片高度的情况下,每个图片所在的item盒子不好绝对定位.因此在渲染页面前先获取所有图片的高度,是解决问题的关键点!这里选择用JS中的Im

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

    最近在做移动端h5页面,所以分页什么的就不能按照传统pc端的分页器的思维去做了,这么小的屏幕去点击也不太方便一般来讲移动端都是上拉加载更多,符合正常使用习惯. 首先简单写一下模板部分的html代码,,很简单清晰的逻辑: <template> <div class="loadmore"> <div class="loadmore__body"> <slot></slot> </div> <d

  • vue实现简单瀑布流布局

    vue简单实现瀑布流布局的一种方式(vue瀑布流组件),供大家参考,具体内容如下 vue中的瀑布流布局组件 需求:图片容器宽度固定,高度根据图片自适应,图片一行不能排列时候,换行依次从左往右排列.(瀑布流概念) 另外结合vue-lazy实现懒加载 ( npm i vue-lazyload --save-dev) 使用也很简单,在需要懒加载的img标签上将:src换成v-lazy 父组件传递数据: waterfallData:[ { e_img: "test.jpg", // 图片 e_

  • vue.js组件vue-waterfall-easy实现瀑布流效果

    想必大家应该很多都已经习惯了jquery的DOM操作,jquery的瀑布流实现起来也很容易. 但是,随着时代的发展,随着时代的进步..... 算了算了,扯远了,既然能找到这儿来,肯定是在vue.js上已经有一定的基础了,咱们废话不多说,直接进入主题. vue-waterfall-easy easy! easy! easy! 重要的事情说三遍!!! 所以说,咱们今天用到的不是大家熟知的vue-waterfall,而是vue-waterfall-easy: 一.获取vue-waterfall-eas

  • 通过vue写一个瀑布流插件代码实例

    这篇文章主要介绍了通过vue写一个瀑布流插件代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 效果如图所示: 采用了预先加载图片,再计算高度的办法..网络差的情况下,可能有点卡 新建 vue-water-easy.vue 组件文件 <template> <div class="vue-water-easy" ref="waterWrap"> <div v-for="(i

  • Vue.js 的移动端组件库mint-ui实现无限滚动加载更多的方法

    通过多次爬坑,发现了这些监听滚动来加载更多的组件的共同点, 因为这些加载更多的方法是绑定在需要加载更多的内容的元素上的, 所以是进入页面则直接触发一次,当监听到滚动事件之后,继续加载更多, 所以对于无限滚动加载不需要写首次载入列表的函数, 代码如下: html: //父组件 <div v-infinite-scroll="loadMore" infinite-scroll-disabled="loading" infinite-scroll-distance=

  • vue loadmore组件上拉加载更多功能示例代码

    最近在做移动端h5页面,所以分页什么的就不能按照传统pc端的分页器的思维去做了,这么小的屏幕去点击也不太方便一般来讲移动端都是上拉加载更多,符合正常使用习惯. 首先简单写一下模板部分的html代码,,很简单清晰的逻辑: <template> <div class="loadmore"> <div class="loadmore__body"> <slot></slot> </div> <d

  • Vue瀑布流插件的使用示例

    我自己写的一个的Vue瀑布流插件,列数自适应,不用设置每个卡片的高度. 测试页面:Page.vue 模板页面:WaterFollow.vue 和 WFColumn.vue 在Page.vue中,修改itemW的值,设置每列的最小宽度.例如:itemW="200"(意为200px).list为数组.高度不用设置,:style="{height:item+'px'}"是我为了创造卡片高度加上去的,不加就显示卡片的原来大小. 经测试,created加载数据正常,mount

  • vue制作加载更多功能的正确打开方式

    使用vue制作加载更多功能,通过ajax获取的数据往data里面push经常不成功,原因是push是往数组中追加数据内容的,而不能用作数组之间的拼接,ajax获取的数据就是数组形式的,因此不成功,应该使用concat()拼接两个数组. //这是错误的写法 $.ajax({ type:'get', async:false, url:path+'no/noticeMobile/getSendNoticeList?imToken='+ getToken +'&pageFlag=2', dataType

随机推荐