详解vue mint-ui源码解析之loadmore组件


Posted in Javascript onOctober 11, 2017

本文介绍了vue mint-ui源码解析之loadmore组件,分享给大家,具体如下:

接入

官方接入文档mint-ui loadmore文档

接入使用Example

html

<div id="app">
  <mt-loadmore :top-method="loadTop" :bottom-method="loadBottom" :bottom-all-loaded="allLoaded" :max-distance="150"
         @top-status-change="handleTopChange" ref="loadmore">

    <div 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>
    </div>

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

  </mt-loadmore>
</div>

css

<link rel="stylesheet" href="https://unpkg.com/mint-ui/lib/style.css" rel="external nofollow" >
*{
  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

<div class="mint-loadmore">
  <div class="mint-loadmore-content" :class="{ 'is-dropped': topDropped || bottomDropped}" :style="{ 'transform': 'translate3d(0, ' + translate + 'px, 0)' }">
   <slot name="top">
    <div 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>
    </div>
   </slot>
   <slot></slot>
   <slot name="bottom">
    <div 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>
    </div>
   </slot>
  </div>
 </div>

关于 上面的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 = '';
 }
}

总结

  1. 下拉刷新和上拉加载更多的实现原理可以借鉴
  2. getScrollEventTarget()获取滚动对象,getScrollTop()获取滚动距离,checkBottomReached()判断是否滚动到底部;这三种方式可以借鉴
  3. 缺点: 写死了top slot 和 bottom slot的高度,太不灵活;这个地方可以优化

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

Javascript 相关文章推荐
jQuery EasyUI API 中文文档 - Parser 解析器
Sep 29 Javascript
在JavaScript里嵌入大量字符串常量的实现方法
Jul 07 Javascript
javascript获取选中的文本的方法代码
Oct 30 Javascript
js获取触发事件元素在整个网页中的绝对坐标(示例代码)
Dec 13 Javascript
JS判断移动端访问设备并加载对应CSS样式
Jun 13 Javascript
JavaScript实现继承的4种方法总结
Oct 16 Javascript
js实现绿白相间竖向网页百叶窗动画切换效果
Mar 02 Javascript
jQuery实现表格展开与折叠的方法
May 04 Javascript
Javascript实现网络监测的方法
Jul 31 Javascript
AngularJS自定义服务与fliter的混合使用
Nov 24 Javascript
JS+html5制作简单音乐播放器
Sep 13 Javascript
vue2.0设置proxyTable使用axios进行跨域请求的方法
Oct 19 Javascript
JS随机排序数组实现方法分析
Oct 11 #Javascript
vue mint-ui学习笔记之picker的使用
Oct 11 #Javascript
jQuery中过滤器的基本用法示例
Oct 11 #jQuery
基于VUE.JS的移动端框架Mint UI的使用
Oct 11 #Javascript
jQuery中extend函数简单用法示例
Oct 11 #jQuery
vue中配置mint-ui报css错误问题的解决方法
Oct 11 #Javascript
node.js操作MongoDB的实例详解
Oct 11 #Javascript
You might like
信用卡效验程序
2006/10/09 PHP
php计算多维数组中所有值总和的方法
2015/06/24 PHP
php实现在多维数组中查找特定value的方法
2015/07/29 PHP
PDO的安全处理与事物处理方法
2016/10/31 PHP
php根据用户名和手机号查询是否存在手机号码
2017/02/16 PHP
PHP添加文字水印或图片水印的水印类完整源代码与使用示例
2019/03/18 PHP
php使用filter_var函数判断邮箱,url,ip格式示例
2019/07/06 PHP
js的一些常用方法小结
2011/06/29 Javascript
js数组中如何随机取出一个值
2014/06/13 Javascript
使用jquery 简单实现下拉菜单
2015/01/14 Javascript
JS中的二叉树遍历详解
2016/03/18 Javascript
Bootstrap栅格系统的使用和理解2
2016/12/14 Javascript
原生js实现对Ajax的封装(仿jquery)
2017/01/22 Javascript
详解使用fetch发送post请求时的参数处理
2017/04/05 Javascript
Vue实现数字输入框中分割手机号码的示例
2017/10/10 Javascript
JS设计模式之访问者模式定义与用法分析
2018/02/05 Javascript
微信小程序在地图选择地址并返回经纬度简单示例
2018/12/03 Javascript
[47:21]Liquid vs TNC Supermajor 胜者组 BO3 第一场 6.4
2018/06/05 DOTA
Python自动重试HTTP连接装饰器
2015/04/28 Python
python之验证码生成(gvcode与captcha)
2019/01/02 Python
Python中sys模块功能与用法实例详解
2020/02/26 Python
python能否java成为主流语言吗
2020/06/22 Python
python之随机数函数的实现示例
2020/12/30 Python
澳大利亚最好的电动自行车:Leon Cycle
2020/12/19 全球购物
linux面试题参考答案(6)
2014/08/29 面试题
旅游个人求职信范文
2014/01/30 职场文书
信息管理应届生求职信
2014/03/07 职场文书
品牌宣传方案
2014/03/21 职场文书
高中生操行评语
2014/04/25 职场文书
总经理助理岗位职责范本
2014/07/20 职场文书
高中学生会竞选演讲稿
2014/08/25 职场文书
党的群众路线批评与自我批评发言稿
2014/10/16 职场文书
青岛海底世界导游词
2015/02/11 职场文书
热血教师观后感
2015/06/10 职场文书
2019年教师节祝福语精选,给老师送上真诚的祝福
2019/09/09 职场文书
mysql 8.0.24 安装配置方法图文教程
2021/05/12 MySQL