下拉刷新-掘友自制流程👇🏼

2,494 阅读3分钟

效果展示

扫码体验

点击访问demo地址
点击查看源码

背景

下拉刷新是移动端经常会用到的功能,看了各种各样的ui库,自己想捣鼓一下,研究下其中的原理,于是便有了这个例子。

页面骨架

<template>
  <div class="pullDown">
    <!--loading-->
    <div class="pullDown__loading"></div>
    <!--scroll content-->
    <div class="pullDown__scroll">
        <ul class="pullDown__list"></ul>
    </div>
  </div>
</template>

页面基本样式

.pullDown{
    background-color: #fff;
    position: absolute;
    z-index: 1;
    width: 100%;
    height: 100%;
    overflow-y: scroll;
    /* 增加该属性,可以增加弹性 */
    -webkit-overflow-scrolling: touch;
    transform: translate3d(0, 0, 0);
    -webkit-transform: translate3d(0, 0, 0);    
}
.pullDonw__loading{
    position: absolute;
    left: 0;
    top: 0;
    display: block;
    overflow: hidden;
    width: 100%;
    height: 50px;
    text-align: center;
    padding: 16px 24px; 
    z-index: 2;
    background-color: #fff;
}
.pullDown__scroll{
    position: relative;
    z-index: 10;
    background-color: #fff;
    border-bottom: 1px solid #e8e8e8;
    border-top: 1px solid #e8e8e8;
}

核心实现

下拉刷新主要根据touchstart,touchmove,touchend三个事件进行判断操作。

  1. touchstar触发时,则判断刷新状态中和容器到达顶端的情况,否则记录当前位置信息并设置可拉动状态;
  2. touchemove触发时,判断刷新状态中和可拉动状态,且当前移动位置需大于开始位置,则设置容器跟随手指滚动;
  3. touchend触发时,判断刷新状态中和动画中,分两种情况:
    • 拉动距离超过刷新情况,则执行动画,刷新动作
    • 拉动距离没超过刷新情况,则返回原始状态
touchstart($event) {
    var vm= this;
    //判断是否允许下拉
    if (!vm.isPull && !vm.isLoading && vm._scrollWrap.scrollTop == vm.offsetTop) {
      vm.isPull= true;
      vm.startPos= $event.touches[0].pageY;
    };
},
touchmove($event) {
    var vm= this;
    vm.endPos= $event.touches[0].pageY;
    
    //是否能拉动
    if (!vm.isLoading && vm.isPull && vm.startPos<vm.endPos) {
    
      vm.distance= (vm.endPos- vm.startPos)*vm.modulus;
      var offsetLoading= vm._loadingWrap.offsetHeight;
    
      if (vm.distance >= offsetLoading) {
        vm.pullStatus = 'up'
      }else{
        vm.pullStatus = 'down'
      }
      vm.moveTransition(vm.$refs.scroll, vm.distance, 0);
    }
},
touchend($event) {
    var vm= this;
    var offsetLoading= vm._loadingWrap.offsetHeight;
    // vm.isPull= false;
    
    //加载中 或 移动距离小于0不能移动
    if (vm.isLoading || vm.distance<=0 || vm.isBack) {return false;}
    
    //拉动距离大于临界值
    if ( vm.distance>0 && vm.distance >= offsetLoading) {
    
      //执行刷新动作
      vm.pullTransition({
        dom: vm._scrollWrap,
        begin: offsetLoading,
        end: vm.distance,
        duration: 500
      });
    
      //后台刷新数据
      vm.refresh();
    }else{
    
      vm.isBack= true;
      // 返回back初始状态
      vm.pullTransition({
        dom: vm._scrollWrap,
        end: vm.distance,
        duration: 500,
        callback: vm.resetStatus
      });
    }
}

下拉动画实现

采用css动画,则难以精准把握动画结束的时间。故采用RequestAnimationFrame实现动画,可监听到结束状态并处理回调。有关 requestAnimationFrame参考以下资料:

/**
* [pullTransition 动画拉动]
* @param  {[type]} dom [元素]
* @param  {[type]} begin [起始值]
* @param  {[type]} end [结束值]
* @param  {[type]} duration [时间]
* @param  {[type]} callback [回调函数]
* @return {[type]}          [description]
*/
pullTransition(opt) {
    var vm= this;
    if (!opt.dom || !opt.end) {
      console.error('参数不足');
      return false;
    };
    
    var opt= {
      dom: opt.dom,
      currentTime: 0,
      begin: opt.begin? opt.begin: 0,
      end: opt.end,
      //相当一部分的浏览器的显示频率是16.7ms 约等于17
      duration: opt.duration? Math.ceil(opt.duration / 17) : 0,
      callback: opt.callback || false
    };
    
    var step= function() {
    
      //根据缓动算法取得值
      var value = opt.end-vm.easeInOut(opt.currentTime, opt.begin, opt.end-opt.begin, opt.duration)+opt.begin;
      opt.dom.style.transform= 'translate3d(0,'+value+'px,0)';
    
      opt.currentTime++;
    
      //判断是否到时间
      if (opt.currentTime <= opt.duration) {
           // 继续运动
           requestAnimationFrame(step);
      } else {
          // 动画结束
          if(opt.callback) opt.callback();
      } 
    };
    step();
}
/**
* [Linear 缓动算法]
* @param {[type]} t [current time(当前时间)]
* @param {[type]} b [beginning value(初始值)]
* @param {[type]} c [change in value(变化量)]
* @param {[type]} d [duration(持续时间)]
*/
easeInOut(t, b, c, d) {
    if ((t /= d / 2) < 1) return c / 2 * t * t*t + b;
    return c / 2*((t -= 2) * t * t + 2) + b;      
}

微信下拉显示网址处理

在微信打开一个页面时,在顶部用力往下拉的时候会出现如下图情况,对于下拉刷新来说体验比较差。我在网上搜索了下找到两种方式处理,供各位参考。本例子采用第一种方式,至于采用的依据目前还未深入比较,后续再深入调查。


至此,下拉刷新功能完成,任何意见和建议欢迎提出。