上拉加载下拉刷新(better-scroll)

标签: better-scroll  上拉加载下拉刷新  vue  scroll

前端实现一个上拉加载,下拉刷新,并且滑动一定的位置能够实现回到顶部的功能。这个在移动端上来说是一个很常见的功能。之前有看到有人用bette-scroll库来实现这个功能。
最基本的初始化代码如下:

import BScroll from 'better-scroll'
let wrapper = document.querySelector('.wrapper')
let scroll = new BScroll(wrapper)

查阅了better-scroll的api,发现它文档中提供了pullDownRefresh和pullUpLoad两个属性值。
(pullDownRefresh、pullUpLoad、click、probeType概念摘抄自官网:https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/#better-scroll 是什么)

pullDownRefresh

这个配置用于做下拉刷新功能,默认为 false。当设置为 true 或者是一个 Object 的时候,可以开启下拉刷新。

pullDownRefresh: {
  threshold: 50,
  stop: 20
}

可以配置顶部下拉的距离(threshold) 来决定刷新时机以及回弹停留的距离(stop)。当下拉刷新数据加载完毕后,需要执行 finishPullDown 方法。

pullUpLoad

这个配置用于做上拉加载功能,默认为 false。当设置为 true 或者是一个 Object 的时候,可以开启上拉加载。

pullUpLoad: {
  threshold: 50
}

可以配置离(threshold)来决定开始加载的时机。当上拉加载数据加载完毕后,需要执行 finishPullUp 方法。

click

better-scroll 默认会阻止浏览器的原生 click 事件。当设置为 true,better-scroll 会派发一个 click 事件,我们会给派发的 event 参数加一个私有属性 _constructed,值为 true。

probeType

有时候我们需要知道滚动的位置。当 probeType 为 1 的时候,会非实时(屏幕滑动超过一定时间后)派发scroll 事件;当 probeType 为 2 的时候,会在屏幕滑动的过程中实时的派发 scroll 事件;当 probeType 为 3 的时候,不仅在屏幕滑动的过程中,而且在 momentum 滚动动画运行过程中实时派发 scroll 事件。如果没有设置该值,其默认值为 0,即不派发 scroll 事件。

对于pullDownRefresh、pullUpLoad,也参照了一些大牛的意见。这里面阈值的设置也要根据对于不同缩放程度的屏幕,乘以对应的缩放比。

 export const getDeviceRatio () => {
    var isAndroid = window.navigator.appVersion.match(/android/gi);
    var isIPhone = window.navigator.appVersion.match(/iphone/gi);
    var devicePixelRatio = window.devicePixelRatio;
    var dpr;
    if (isIPhone) {
        // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
        if (devicePixelRatio >= 3) {                
            dpr = 3;
        } else if (devicePixelRatio >= 2){
            dpr = 2;
        } else {
            dpr = 1;
        }
    } else {
        // 其他设备下,仍旧使用1倍的方案
        dpr = 1;
    }
    return dpr
}

scrollTo(x, y, time, easing)

参数:
{Number} x 横轴坐标(单位 px)
{Number} y 纵轴坐标(单位 px)
{Number} time 滚动动画执行的时长(单位 ms)
{Object} easing 缓动函数,一般不建议修改,如果想修改,参考源码中的 ease.js 里的写法
返回值:无
作用:滚动到指定的位置。

//父组件的方法
backTop() {
        this.$refs.scroll.toTop()
 },
 //子组件的方法 (回到顶部)
 toTop() {
    this.scroll.scrollTo(0,0)
  },

事件监听

pullingDown

触发时机:在一次下拉刷新的动作后,这个时机一般用来去后端请求数据。

this.scroll.on('pullingDown',()=>{
	//do something
})

pullingUp

触发时机:在一次上拉加载的动作后,这个时机一般用来去后端请求数据。

this.scroll.on('pullingUp',()=>{
	//do something
})

scroll

因为better-scroll 会默认阻止对原生scroll事件的监听,为实现滑动到一定位置后,显示回到顶部的悬浮窗。

this.scroll.on('scroll',(e)=>{
       let status = this.scroll.y < -200
        this.$emit('showIcon', status)
})

开发过程中,为了防止多次触发,需要加2个控制类的东西:(目的是为了保证每次只有一个事件在进行)

let onPullUp=true;
let onPullDown=true;

demo Code

<template>
  <div>
    <scroller v-if="dataList.length > 0"
              id="scroll"
              ref="scroll"
              :top="top"
              :stillHave="stillHave"
              :dataList="dataList"
              :refreshTime="refreshTime"
              :pullDownRefresh="DOWN_CONFIG"
              :pullUpLoad="UP_CONFIG"
              @showIcon="showIcon"
              @onPullUp="pullUpHandle"
              @onPullDown="pullDownHandle">
      <div class="list" slot="list">
        <video-list :list="dataList"></video-list>
      </div>
    </scroller>
    <div class="fix-icon" v-if="iconShow"  @click="backTop">
    </div>
  </div>
</template>

<script>
  import {
    DOWN_CONFIG,
    UP_CONFIG,
    DEVICE_RATIO
  } from "../util/api";

  import VideoList from "./VideoList";
  import Scroller from "./Scroller";
  import {dateFormat} from "../util/DateFormat";

  export default {
    components: {
      Scroller,
      VideoList
    },
    name: "scroll-page",
    props:{
      dataList:{
        type:Array,
        default:function () {
          return []
        }
      },
      top:{
        type:Number,
        default:0
      },
      isSecShow:{
        type:Boolean,
        default:true
      },
      tabIndex:{
        type:Number,
        default:0
      },
      stillHave:{
        type:Boolean,
        default:false
      },
      loading:{
        type:Boolean,
        default:false
      }
    },
    data() {
      return{
        list:[],
        DOWN_CONFIG,
        UP_CONFIG,
        DEVICE_RATIO,
        refreshTime:dateFormat().format('YYYY年MM月DD日 HH:mm:ss'),
        iconShow:false
      }
    },
    created() {
      this.initList()
    },
    computed:{
      scrollElement(){
        return this.$refs.scroll
      }
    },
    methods:{
      initList() {
        this.list = this.dataList
      },
       showIcon(status) {
        this.iconShow = status
      },
      backTop() {
        this.$refs.scroll.toTop()
      },
      pullUpHandle(val){
        this.scrollElement.PullingUpWord="加载完成";
        setTimeout(()=>{
          this.scrollElement.finish("PullUp");
          if(this.stillHave){
            this.$emit('getMoreList',0)
          }
        },1000)
      },
      pullDownHandle(val){
        if(!this.isSecShow){
          this.scrollElement.finish("PullDown");
          this.tempClick()
        }else {
          setTimeout(()=>{
            this.scrollElement.finish("PullDown");
            this.refreshTime = dateFormat().format('YYYY年MM月DD日 HH:mm:ss')
            this.$emit('getMoreList',1)
          },2000)
        }
      },
      tempClick() {
        this.$emit('pickStatus')
      }
    },
    watch:{
      tabIndex:{
        handler(v1,v2){
          if(v1 !=v2){
            this.initList()
          }
        },
        immediate: true
      }
    }
  }
</script>

<style lang="scss" scoped>
  .list {
    height: 100%;
    position: relative;
  }
  .fix-icon {
    z-index: 999;
    width: px2Vw(40);
    height: px2Vw(40);
    background-size: cover;
    background-image: url("../assets/images/back-top.png");
    border-radius: 100%;
    position: absolute;
    bottom: 0;
    right: px2Vw(15);
  }
</style>
<template>
  <div ref="wrapper" class="list-wrapper" :style="{top:0+'px',bottom: 0 +'px'}">
    <div class="scroll-content">
      <slot name="list"></slot>
      <div>
        <pulling-words v-show="!inPullUp && stillHave" :loadingText="beforePullUpWord"></pulling-words>
        <pulling-words  v-show="!inPullUp && !stillHave && dataList.length " :loadingText="'- 已经到底啦 -'"></pulling-words>
        <loading v-show="inPullUp" :loadingWord="PullingUpWord" :text="''"></loading>
      </div>
    </div>

    <transition name="pullDown">
      <loading class="pullDown" v-show="inPullDown" :loadingWord="refreshTime" :text="'上次刷新时间:'"></loading>
    </transition>
  </div>
</template>

<script>

  import Loading from "./loading/Loading";

  const  PullingUpWord="正在拼命加载中...";
  const  beforePullUpWord="上拉加载更多";
  const  finishPullUpWord="加载完成";

  const  PullingDownWord="加载中...";

  import BScroll from 'better-scroll'
  import PullingWords from "./pullingWords/PullingWords";



  export default {
    components: {
      PullingWords,
      Loading
    },
    name: "scroller",
    props:{
      dataList:{
        type: Array,
        default: []
      },
      stillHave:{
        type:Boolean,
        default:true
      },
      probeType: {
        type: Number,
        default: 3
      },
      click: {
        type: Boolean,
        default: true
      },
      pullDownRefresh: {
        type: null,
        default: false
      },
      pullUpLoad: {
        type: null,
        default: false
      },
      refreshTime:{
        type:String,
        default:''
      }
    },
    data(){
      return{
        scroll:null,
        inPullUp:false,
        inPullDown:false,
        beforePullUpWord,
        PullingUpWord,
        PullingDownWord,
        continuePullUp:true,
        fixIconShow:false,
        box:{},
        tempShow:false
      }
    },
    computed:{

    },
    mounted() {
      setTimeout(()=>{
        this.initScroll();

        this.scroll.on('scroll',(e)=>{
          let status = this.scroll.y < -200
          this.$emit('showIcon', status)
        })

        this.scroll.on('pullingUp',()=> {
          if(this.continuePullUp){
            this.beforePullUp();
            this.$emit("onPullUp");
          }
        });

        this.scroll.on('pullingDown',()=> {
          this.beforePullDown();
          this.$emit("onPullDown");
        });


      },20)


    },
    methods:{
      toTop() {
        this.scroll.scrollTo(0,0)
      },

      initScroll() {
        if (!this.$refs.wrapper) {
          return
        }
        this.scroll = new BScroll(this.$refs.wrapper, {
          probeType: this.probeType,
          click: this.click,
          pullDownRefresh: this.pullDownRefresh,
          pullUpLoad: this.pullUpLoad,
        })
      },
      beforePullUp(){
        this.PullingUpWord = PullingUpWord;
        this.inPullUp=true;
      },
      beforePullDown(){
        this.disable();
        this.inPullDown=true;
      },
      finish(type){
        this["finish"+type]();
        this.enable();
        this["in"+type]=false;
      },
      disable() {
        this.scroll && this.scroll.disable()
      },
      enable() {
        this.scroll && this.scroll.enable()
      },
      refresh() {
        this.scroll && this.scroll.refresh()
      },
      finishPullDown(){
        this.scroll&&this.scroll.finishPullDown()
      },
      finishPullUp(){
        this.scroll&&this.scroll.finishPullUp()
      },
    }
  }
</script>

<style lang="scss" scoped>
  .list-wrapper{
    position: absolute;
    left: 0;
    right:0;
    overflow: hidden;
    background: #f7f8f9;
  }
  .scroll-content {
    min-height: calc(100% + 1px );
  }

  .pullDown{
    position: absolute;
    top:0;
    left:0;
  }

  .pullDown-enter-active{
    transition:all 0.2s;
  }

  .pullDown-enter, .pullDown-leave-active{
    transform:translateY(-100%);
    transition:all 0.2s;
  }

</style>

代码中使用父子组件,父组件通过this.refspropsthis.refs去执行子组件里面的方法,通过props将参数传递给子组件。而子组件通过this.emit来去执行父组件中的方法。
在这里插入图片描述
在这里插入图片描述
项目的完成demo下载地址:https://github.com/yzbyxmx/slide-demo