当前位置: 移动技术网 > IT编程>网页制作>CSS > VUE2配合mint-ui实现自定义loadmore(下拉刷新,上拉更多)教程

VUE2配合mint-ui实现自定义loadmore(下拉刷新,上拉更多)教程

2018年02月22日  | 移动技术网IT编程  | 我要评论

boramcity,北京哪脱毛比较好,龙珠激斗1.6

html

<p id="app">

    <mt-loadmore :top-method="loadTop" :bottom-method="loadBottom" :bottom-all-loaded="allLoaded" :max-distance="150"

                 @top-status-change="handleTopChange" ref="loadmore">

        <p slot="top" class="mint-loadmore-top">

            <span v-show="topStatus === 'pull'" :class="{ 'rotate': topStatus === 'drop' }">↓</span>

            <span v-show="topStatus === 'loading'">Loading...</span>

            <span v-show="topStatus === 'drop'">释放更新</span>

        </p>

        <ul class="scroll-wrapper">

            <li v-for="item in list" @click="itemClick(item)">{{ item }}</li>

        </ul>

    </mt-loadmore>

</p>

css

<link rel="stylesheet" href="https://unpkg.com/mint-ui/lib/style.css">

*{

    margin: 0;

    padding: 0;

}

html, body{

    height: 100%;

}

#app{

    height: 100%;

    overflow: scroll;

}

.scroll-wrapper{

    margin: 0;

    padding: 0;

    list-style: none;

}

.scroll-wrapper li{

    line-height: 120px;

    font-size: 60px;

    text-align: center;

}

js 

<!-- 先引入 Vue -->

<script src="https://unpkg.com/vue/dist/vue.js"></script>

<!-- 引入组件库 -->

<script src="https://unpkg.com/mint-ui/lib/index.js"></script>

<script>

    new Vue({

        el: '#app',

        data: {

            list: [],

            allLoaded: false,

            topStatus: ''

        },

        created: function () {

            var i =0, len=20;

            for (; i< len; i++){

                this.list.push('demo' + i);

            }

        },

        methods: {

            loadTop: function () {  // 刷新数据的操作

                var self = this;

                setTimeout(function () {

                    self.list.splice(0, self.list.length);

                    var i =0, len=20;

                    for (; i< len; i++){

                        self.list.push('demo' + i);

                    }

                    self.$refs.loadmore.onTopLoaded();

                }, 2000);

            },

            loadBottom: function () { // 加载更多数据的操作

                //load data

                //this.allLoaded = true;// 若数据已全部获取完毕

                var self = this;

                setTimeout(function () {

                    var i =0; len = 10;

                    for (; i< len; i++){

                        self.list.push('dddd' + i);

                    }

                    self.$refs.loadmore.onBottomLoaded();

                }, 2000);

            },

            handleTopChange: function (status) {

                this.topStatus = status;

            },

            itemClick: function (data) {

                console.log('item click, msg : ' + data);

            }

        }

    });

</script>

实现原理解析

布局原理 

loadmore组件内部由三个slot组成,分别为name=top,name=bottom,default;

top用于展示下拉刷新不同状态展示的内容,初始设置margin-top为-top的高度来将自己隐藏

bottom同top,用于展示上拉加载更多不同状态展示的内容

default填充滚动详细内容

实现原理 

主要是通过js的touch事件的监听来实现

在touchmove事件,如果是向下滑动并且滚动的dom的scrollTop为0,那么整个组件向下偏移(滑动的距离/比值)展示出top solt的内容

在touchmove时间,如果是向上滑动并且滑动到了底部,再继续滑动整个组件向上偏移(滑动的距离/比值)展示出bottom solt的内容

源码解析

组件的template html

  <p class="mint-loadmore">

    <p class="mint-loadmore-content" :class="{ 'is-dropped': topDropped || bottomDropped}" :style="{ 'transform': 'translate3d(0, ' + translate + 'px, 0)' }">

      <slot name="top">

        <p class="mint-loadmore-top" v-if="topMethod">

          <spinner v-if="topStatus === 'loading'" class="mint-loadmore-spinner" :size="20" type="fading-circle"></spinner>

          <span class="mint-loadmore-text">{{ topText }}</span>

        </p>

      </slot>

      <slot></slot>

      <slot name="bottom">

        <p class="mint-loadmore-bottom" v-if="bottomMethod">

          <spinner v-if="bottomStatus === 'loading'" class="mint-loadmore-spinner" :size="20" type="fading-circle"></spinner>

          <span class="mint-loadmore-text">{{ bottomText }}</span>

        </p>

      </slot>

    </p>

  </p>

关于 上面的spinner标签,是一个组件,这里不做详细介绍。top solt和bottom slot中的内容是展示的内容,可以通过外部自定义的方式传入。 

其实它的实现有一个很严重的弊端,就是写死了top solt和bottom slot的高度为50px,而且js中的处理也是使用50px进行的逻辑处理。所以并满足我们开发中自定义top slot 和bottom slot的需求。

js核心解析

props解析 

关于props的解析,可以参考mint-ui的官方文档

data解析

data() {

  return {

    translate: 0, // 此变量决定当前组件上下移动,

    scrollEventTarget: null, // 滚动的dom节点

    containerFilled: false, // 当前滚动的内容是否填充完整,不完成会调用 loadmore的回调函数

    topText: '', // 下拉刷新,显示的文本

    topDropped: false, // 记录当前drop状态,用给组件dom添加is-dropped class(添加回到原点的动画)

    bottomText: '', // 上拉加载更多 显示的文本

    bottomDropped: false, // 同topDropped

    bottomReached: false, // 当前滚动是否滚动到了底部

    direction: '', // touch-move过程中, 当前滑动的方向

    startY: 0, // touch-start 起始的y的坐标值

    startScrollTop: 0, // touch-start 起始的滚动dom的 scrollTop

    currentY: 0, // touch-move 过程中的 y的坐标

    topStatus: '', // 下拉刷新的状态: pull(下拉) drop(释放) loading(正在加载数据)

    bottomStatus: '' // 上拉加载更多的状态: 状态同上

  };

}

上面的关于每个data数据的具体作用通过注释做了详细说明。

watch解析

watch: {

  topStatus(val) {

    this.$emit('top-status-change', val);

    switch (val) {

      case 'pull':

        this.topText = this.topPullText;

        break;

      case 'drop':

        this.topText = this.topDropText;

        break;

      case 'loading':

        this.topText = this.topLoadingText;

        break;

    }

  },

  bottomStatus(val) {

    this.$emit('bottom-status-change', val);

    switch (val) {

      case 'pull':

        this.bottomText = this.bottomPullText;

        break;

      case 'drop':

        this.bottomText = this.bottomDropText;

        break;

      case 'loading':

        this.bottomText = this.bottomLoadingText;

        break;

    }

  }

}

上面是组件通过watch监听的两个变量,后面我们能看到他们的改变是在touchmove事件中进行处理改变的。它的作用是通过它的变化来改变top slot和bottom slot的文本内容; 

同时发出status-change事件给外部使用,因为可能外部自定义top slot 和bottom slot的内容,通过此事件来通知外部当前状态以便外部进行处理。

核心函数的解析 

这里就不将所有的method列出,下面就根据处理的所以来解析对应的method函数。 

首先,入口是在组件mounted生命周期的钩子回调中执行init函数

mounted() {

  this.init();// 当前 vue component挂载完成之后, 执行init()函数

}

init函数:

init() {

    this.topStatus = 'pull';

    this.bottomStatus = 'pull';

    this.topText = this.topPullText;

    this.scrollEventTarget = this.getScrollEventTarget(this.$el); // 获取滚动的dom节点

    if (typeof this.bottomMethod === 'function') {

      this.fillContainer(); // 判断当前滚动内容是否填满,没有执行外部传入的loadmore回调函数加载数据

      this.bindTouchEvents(); // 为当前组件dom注册touch事件

    }

    if (typeof this.topMethod === 'function') {

      this.bindTouchEvents();

    }

  },

  fillContainer() {

    if (this.autoFill) {

      this.$nextTick(() => {

        if (this.scrollEventTarget === window) {

          this.containerFilled = this.$el.getBoundingClientRect().bottom >=

            document.documentElement.getBoundingClientRect().bottom;

        } else {

          this.containerFilled = this.$el.getBoundingClientRect().bottom >=

            this.scrollEventTarget.getBoundingClientRect().bottom;

        }

        if (!this.containerFilled) { // 如果没有填满内容, 执行loadmore的操作

          this.bottomStatus = 'loading';

          this.bottomMethod();// 调用外部的loadmore函数,加载更多数据

        }

      });

    }

  }

init函数主要是初始化状态和事件的一些操作,下面着重分析touch事件的回调函数的处理。 

首先touchstart事件回调处理函数

  handleTouchStart(event) {

    this.startY = event.touches[0].clientY; // 手指按下的位置, 用于下面move事件计算手指移动的距离

    this.startScrollTop = this.getScrollTop(this.scrollEventTarget); // 起始scroll dom的 scrollTop(滚动的距离)

    //下面重置状态变量

    this.bottomReached = false;

    if (this.topStatus !== 'loading') {

      this.topStatus = 'pull';

      this.topDropped = false;

    }

    if (this.bottomStatus !== 'loading') {

      this.bottomStatus = 'pull';

      this.bottomDropped = false;

    }

  }

主要是记录初始位置和重置状态变量。 

下面继续touchmove的回调处理函数

  handleTouchMove(event) {

    //确保当前touch节点的y的位置,在当前loadmore组件的内部

    if (this.startY < this.$el.getBoundingClientRect().top && this.startY > this.$el.getBoundingClientRect().bottom) {

      return;

    }

    this.currentY = event.touches[0].clientY;

    let distance = (this.currentY - this.startY) / this.distanceIndex;

    this.direction = distance > 0 ? 'down' : 'up';

    // 下拉刷新,条件(1.外部传入了刷新的回调函数 2.滑动方向是向下的 3.当前滚动节点的scrollTop为0 4.当前topStatus不是loading)

    if (typeof this.topMethod === 'function' && this.direction === 'down' &&

      this.getScrollTop(this.scrollEventTarget) === 0 && this.topStatus !== 'loading') {

      event.preventDefault();

      event.stopPropagation();

      //计算translate(将要平移的距离), 如果当前移动的距离大于设置的最大距离,那么此次这次移动就不起作用了

      if (this.maxDistance > 0) {

        this.translate = distance <= this.maxDistance ? distance - this.startScrollTop : this.translate;

      } else {

        this.translate = distance - this.startScrollTop;

      }

      if (this.translate < 0) {

        this.translate = 0;

      }

      this.topStatus = this.translate >= this.topDistance ? 'drop' : 'pull';// drop: 到达指定的阈值,可以执行刷新操作了

    }

    // 上拉操作, 判断当前scroll dom是否滚动到了底部

    if (this.direction === 'up') {

      this.bottomReached = this.bottomReached || this.checkBottomReached();

    }

    if (typeof this.bottomMethod === 'function' && this.direction === 'up' &&

      this.bottomReached && this.bottomStatus !== 'loading' && !this.bottomAllLoaded) {

      event.preventDefault();

      event.stopPropagation();

      // 判断的逻辑思路同上

      if (this.maxDistance > 0) {

        this.translate = Math.abs(distance) <= this.maxDistance

          ? this.getScrollTop(this.scrollEventTarget) - this.startScrollTop + distance : this.translate;

      } else {

        this.translate = this.getScrollTop(this.scrollEventTarget) - this.startScrollTop + distance;

      }

      if (this.translate > 0) {

        this.translate = 0;

      }

      this.bottomStatus = -this.translate >= this.bottomDistance ? 'drop' : 'pull';

    }

    this.$emit('translate-change', this.translate);

  }

上面的代码逻辑挺简单,注释也就相对不多。 

重点谈一下checkBottomReached()函数,用来判断当前scroll dom是否滚动到了底部。

  checkBottomReached() {

    if (this.scrollEventTarget === window) {

      return document.body.scrollTop + document.documentElement.clientHeight >= document.body.scrollHeight;

    } else {

      return this.$el.getBoundingClientRect().bottom <= this.scrollEventTarget.getBoundingClientRect().bottom + 1;

    }

  }

经过我的测试,上面的代码是有问题: 

当scrollEventTarget是window的条件下,上面的判断是不对的。因为document.body.scrollTop总是比正常值小1,所以用于无法满足到达底部的条件; 

当scrollEventTarget不是window的条件下,上面的判断条件也不需要在this.scrollEventTarget.getBoundingClientRect().bottom后面加1,但是加1也不会有太大视觉上的影响。 

最后来看下moveend事件回调的处理函数

  handleTouchEnd() {

    if (this.direction === 'down' && this.getScrollTop(this.scrollEventTarget) === 0 && this.translate > 0) {

      this.topDropped = true; // 为当前组件添加 is-dropped class(也就是添加动画处理)

      if (this.topStatus === 'drop') { // 到达了loading的状态

        this.translate = '50'; // top slot的高度

        this.topStatus = 'loading';

        this.topMethod(); // 执行回调函数

      } else { // 没有到达,回调原点

        this.translate = '0';

        this.topStatus = 'pull';

      }

    }

    // 处理逻辑同上

    if (this.direction === 'up' && this.bottomReached && this.translate < 0) {

      this.bottomDropped = true;

      this.bottomReached = false;

      if (this.bottomStatus === 'drop') {

        this.translate = '-50';

        this.bottomStatus = 'loading';

        this.bottomMethod();

      } else {

        this.translate = '0';

        this.bottomStatus = 'pull';

      }

    }

    this.$emit('translate-change', this.translate);

    this.direction = '';

  }

}

总结

下拉刷新和上拉加载更多的实现原理可以借鉴

getScrollEventTarget()获取滚动对象,getScrollTop()获取滚动距离,checkBottomReached()判断是否滚动到底部;这三种方式可以借鉴

缺点: 写死了top slot 和 bottom slot的高度,太不灵活;这个地方可以优化

 

Pull up

在列表底部, 按住 - 上拉 - 释放可以获取更多数据

translate : {{ translate }}

translateScale : {{ moveTranslate }}

 {{ item }} 

 

 

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网