详解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 相关文章推荐
获取页面高度,窗口高度,滚动条高度等参数值getPageSize,getPageScroll
Sep 22 Javascript
javascript 鼠标拖动图标技术
Feb 07 Javascript
Bookmarklet实现启动jQuery(模仿 云输入法)
Sep 15 Javascript
JS+CSS实现带关闭按钮DIV弹出窗口的方法
Feb 27 Javascript
为你的微信小程序体积瘦身详解
May 20 Javascript
js下拉菜单生成器dropMenu使用方法详解
Aug 01 Javascript
JavaScript使用Ajax上传文件的示例代码
Aug 10 Javascript
vue 解决addRoutes动态添加路由后刷新失效问题
Jul 02 Javascript
Angular使用cli生成自定义文件、组件的方法
Sep 04 Javascript
bootstrap自定义样式之bootstrap实现侧边导航栏功能
Sep 10 Javascript
Ant Design Vue 添加区分中英文的长度校验功能
Jan 21 Javascript
详解Vue slot插槽
Nov 20 Vue.js
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
提升PHP执行速度全攻略(下)
2006/10/09 PHP
基于PHP与XML的PDF文档生成技术
2006/10/09 PHP
PHP实现提取一个图像文件并在浏览器上显示的代码
2012/10/06 PHP
PHP制作图形验证码代码分享
2014/10/23 PHP
php生成二维码不保存服务器还有下载功能的实现代码
2018/08/09 PHP
JavaScript表单常用验证集合
2008/01/16 Javascript
常用的javascript function代码
2008/05/23 Javascript
有效提高JavaScript执行效率的几点知识
2015/01/31 Javascript
AngularJS实现表单验证功能详解
2017/10/12 Javascript
一个简易时钟效果js实现代码
2020/03/25 Javascript
使用layer弹窗和layui表单实现新增功能
2018/08/09 Javascript
jquery.pager.js分页实现详解
2019/07/29 jQuery
Vuex的API文档说明详解
2020/02/05 Javascript
JS实现页面数据懒加载
2020/02/13 Javascript
[15:57]教你分分钟做大人:斧王
2014/10/30 DOTA
[01:00:04]DOTA2上海特级锦标赛B组小组赛#1 Alliance VS Spirit第二局
2016/02/26 DOTA
python利用datetime模块计算时间差
2015/08/04 Python
Python 高级专用类方法的实例详解
2017/09/11 Python
Python列表推导式与生成器表达式用法示例
2018/02/08 Python
python顺序的读取文件夹下名称有序的文件方法
2018/07/11 Python
APIStar:一个专为Python3设计的API框架
2018/09/26 Python
Python中logging.NullHandler 的使用教程
2018/11/29 Python
python3使用pandas获取股票数据的方法
2018/12/22 Python
基于Python打造账号共享浏览器功能
2019/05/30 Python
Python3 pandas 操作列表实例详解
2019/09/23 Python
python3 写一个WAV音频文件播放器的代码
2019/09/27 Python
NUK奶瓶美国官网:NUK美国
2016/09/26 全球购物
美国咖啡批发网站:Coffee.org
2017/06/29 全球购物
埃弗顿足球俱乐部官方网上商店:Everton Direct
2018/01/13 全球购物
澳大利亚玩具剧场:Toy Playhouse
2019/03/03 全球购物
Claire’s法国:时尚配饰、美容、珠宝、头发
2021/01/16 全球购物
JNI的定义
2012/11/25 面试题
三年级科学教学反思
2014/01/29 职场文书
民主评议党员工作总结
2014/10/20 职场文书
党纪处分决定书
2015/06/24 职场文书
分享MySQL常用 内核 Debug 几种常见方法
2022/03/17 MySQL