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

来源:互联网 发布:淘宝联盟是不是真的 编辑:程序博客网 时间:2024/05/17 20:24

建议先看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>
原创粉丝点击