基于better-scroll的下拉刷新上推加载更多

目录

前言

项目中使用的截图如下

封装better-scroll

组件的使用


前言

本来想使用vux的scroller组件,但是看到官方说不在维护了,而且在提供的样例里面,虽然例子有可以自定义加载等待区域以及文字提示的内容,但是实际上却没有找到相关样例代码,改来改去也改不成想要的样子。。。最后放弃了。在网上查了很多,后来发现better-scroll这个插件就是为了这种应用场景而生的,简直不能再好。

官方地址:https://ustbhuangyi.github.io/better-scroll/doc/zh-hans/

项目中使用的截图如下

封装better-scroll

我对better-scroll又进行了封装,封装为scroll.vue,代码如下:

<template>
  <div ref="wrapper" class="list-wrapper">
    <div class="scroll-content">
      <div ref="listWrapper">
        <slot>
        </slot>
        <slot name="pullup"
              :pullUpLoad="pullUpLoad"
              :isPullUpLoad="isPullUpLoad"
        >
          <div class="pullup-wrapper" v-if="pullUpLoad">
            <load-more :show-loading="isPullUpLoad"
                       :tip="pullUpTxt">
            </load-more>
          </div>
        </slot>
      </div>
    </div>
    <slot name="pulldown"
          :pullDownRefresh="pullDownRefresh"
          :pullDownStyle="pullDownStyle"
          :beforePullDown="beforePullDown"
          :isPullingDown="isPullingDown"
    >
      <div ref="pulldown" class="pulldown-wrapper" :style="pullDownStyle" v-if="pullDownRefresh">
          <load-more :show-loading="isPullingDown" :tip="refreshTxt"></load-more>
      </div>
    </slot>
  </div>
</template>

<script type="text/ecmascript-6">
  import BScroll from 'better-scroll'
  import {LoadMore} from 'vux'

  const COMPONENT_NAME = 'scroll'

  export default {
    name: COMPONENT_NAME,
    props: {
      data: {
        type:Array,
        default:[]
      }
    },
    data() {
      return {
        pullDownRefresh: {
          threshold: 90,
          stop: 70
        },
        pullUpLoad: {
          threshold: 20
        },
        beforePullDown: true,
        isRebounding: false,
        isPullingDown: false,
        isPullUpLoad: false,
        pullUpDirty: true,
        pullDownStyle: '',
        isLoading:'正在加载',
        pullRefresh:'下拉刷新↓',
        moreTxt:'上推加载更多↑',
        noMoreTxt:'没有更多',
        refreshSuccess:'刷新成功'
      }
    },
    computed: {
      pullUpTxt() {
        if (this.isPullUpLoad) {
          return this.isLoading;
        } else {
          return this.pullUpDirty ? this.moreTxt : this.noMoreTxt;
        }
      },
      refreshTxt(){
        if(this.beforePullDown && !this.isPullingDown){
          return this.pullRefresh;
        }
          if(this.isPullingDown){
            return this.isLoading;
          }else{
            return this.refreshSuccess;
          }
      }
    },
    created() {
      this.pullDownInitTop = -50
    },
    mounted() {
      setTimeout(() => {
        this.initScroll()
      }, 20)
    },
    destroyed() {
      this.$refs.scroll && this.$refs.scroll.destroy()
    },
    methods: {
      initScroll() {
        if (!this.$refs.wrapper) {
          return
        }
        if (this.$refs.listWrapper && (this.pullDownRefresh || this.pullUpLoad)) {
          this.$refs.listWrapper.style.minHeight = `${ctool.getRect(this.$refs.wrapper).height + 1}px`
        }

        let options = {
          probeType: 1,
          scrollbar: {
            fade: true,
            interactive: false
          },
          pullDownRefresh: this.pullDownRefresh,
          pullUpLoad: this.pullUpLoad,
          mouseWheel: true,
          click:true,
          tap:true
        }

        this.scroll = new BScroll(this.$refs.wrapper, options);
        if (this.pullDownRefresh) {
          this._initPullDownRefresh()
        }

        if (this.pullUpLoad) {
          this._initPullUpLoad()
        }
      },
      disable() {
        this.scroll && this.scroll.disable()
      },
      enable() {
        this.scroll && this.scroll.enable()
      },
      refresh() {
        this.scroll && this.scroll.refresh()
      },
      scrollTo() {
        this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments)
      },
      scrollToElement() {
        this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments)
      },
      destroy() {
        this.scroll.destroy()
      },
      forceUpdate(dirty) {
        if (this.pullDownRefresh && this.isPullingDown) {
          this.isPullingDown = false
          this._reboundPullDown().then(() => {
            this._afterPullDown()
          })
        } else if (this.pullUpLoad && this.isPullUpLoad) {
          this.isPullUpLoad = false
          this.scroll.finishPullUp()
          this.pullUpDirty = dirty
          this.refresh()
        } else {
          this.refresh()
        }
      },
      setdirtyfalse(){
        this.pullUpDirty = false;
      },
      _initPullDownRefresh() {
        this.scroll.on('pullingDown', () => {
          this.beforePullDown = false
          this.isPullingDown = true
          this.$emit('pullingDown')
        })

        this.scroll.on('scroll', (pos) => {
          if (!this.pullDownRefresh) {
            return
          }
          if (this.beforePullDown) {
            this.pullDownStyle = `top:${Math.min(pos.y + this.pullDownInitTop, 10)}px`
          }

          if (this.isRebounding) {
            this.pullDownStyle = `top:${10 - (this.pullDownRefresh.stop - pos.y)}px`
          }
        })
      },
      _initPullUpLoad() {
        this.scroll.on('pullingUp', () => {
          this.isPullUpLoad = true
          this.$emit('pullingUp')
        })
      },
      _reboundPullDown() {
        const {stopTime = 600} = this.pullDownRefresh
        return new Promise((resolve) => {
          setTimeout(() => {
            this.isRebounding = true
            this.scroll.finishPullDown()
            resolve()
          }, stopTime)
        })
      },
      _afterPullDown() {
        setTimeout(() => {
          this.pullDownStyle = `top:${this.pullDownInitTop}px`
          this.beforePullDown = true
          this.isRebounding = false
          this.refresh()
        }, this.scroll.options.bounceTime)
      }
    },
    watch: {
      data() {
        setTimeout(() => {
          this.forceUpdate(true)
        }, this.refreshDelay)
      }
    },
    components: {
      LoadMore
    }
  }
</script>

<style>
  .list-wrapper {
    position: relative;
    height: 100%;
    overflow: hidden;
    background: #fff;
  }

  .scroll-content {
    position: relative;
    z-index: 1;
  }

  .pulldown-wrapper {
    position: absolute;
    width: 100%;
    left: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    transition: all;
  }

  .before-trigger {
    margin-top: 5px;
  }

  .after-trigger {
    text-align: center;
    margin-top: 5px;
    width: 100%;
  }
  .loading{
    width: 100%;
  }
  .pullup-wrapper {
    width: 100%;
    display: flex;
    left: 0;
    justify-content: center;
    align-items: center;
    padding: 10px 0;
  }
</style>

这个组件代码,可以直接直接复制出去用。其中initScroll方法中有一个getRect方法,该方法的主要作用是为了设置列表容器的高度一定比父div高,不然就不会出现滚动条,也无法响应下拉刷新的动作。

这里注意下面这两个css

.list-wrapper {
    position: relative;
    height: 100%;
    overflow: hidden;
    background: #fff;
  }

  .scroll-content {
    position: relative;
    z-index: 1;
  }

这两个css的position都是relative,且list-wrapper的高度是100%。这就意味着,他们的高度是等于父div的高度的,而父div的高度就是我们引用这个组件时候,包裹组件的div高度。那么,【划重点】这就要求我们使用的时候,包裹组件的div一定要指定具体的高度,这个高度怎么去,后面会代码说明。

getRect的方法代码如下:

getRect(el) {
    if (el instanceof window.SVGElement) {
      let rect = el.getBoundingClientRect()
      return {
        top: rect.top,
        left: rect.left,
        width: rect.width,
        height: rect.height
      }
    } else {
      return {
        top: el.offsetTop,
        left: el.offsetLeft,
        width: el.offsetWidth,
        height: el.offsetHeight
      }
    }
  },

组件的使用

<template xmlns:v-on="http://www.w3.org/1999/xhtml" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<div>
  <div class="base_div" ref="ldiv">
      <scroll ref="scroll"
              :data="items"
              @pullingDown="onPullingDown"
              @pullingUp="onPullingUp">
        <div class="list-content">
          <div v-for="(item, index) in items"
               v-on:click="toDetail(item)">
            <adapter :data="item"></adapter>
          </div>
        </div>
      </scroll>
    </div>
  </div>
</template>
<script>
  import Scroll from './scroll'
  import adapter from './listAdapter'
export default {
    props: {
        url: String,//ajax请求的url
        page:1,
        params: {//筛选条件
            type: Object,
            default: function () {
              return {};
            }
          }
    },
    data() {
      return {
        items: [],
    },
    mounted() {
      this.$refs.ldiv.style.height = `${ctool.getHeight(this.ui_type)}px`;
    },
    methods: {
      //---下拉刷新滚动相关
      onPullingDown() {
        //下拉刷新
        this.page = 1;
        this.ajaxPost();
      },
      onPullingUp() {
        // 上推加载更多
        this.page++;
        this.ajaxPost();
      },
      ajaxPost(){
        console.log('');
      },
      toDetail(obj){
        console.log('click detail');
      },
    },
    components: {
      Scroll, adapter, Flexbox, FlexboxItem, Search, Popup, XButton, Group, Datetime
    }
}
</script>
<style>
  .search_div {
    border-bottom: solid 1px #E4E4E4;
    min-height: 36px;
  }

  .base_div {
    height: 100%;
    position: relative;
    width: 100%;
    padding-bottom: 2rem;
  }

  .list-content {
    position: relative;
    z-index: 10;
    background: #fff;
  }

</style>

在具体使用的时候,有几点要重点注意,主要是关于css样式的。

关于css的position属性说明,默认情况下,position:static,即正常的按照元素的块级、行排列即可。

常用的还有relative布局和absolute布局。需要说明的是,relative布局是相对于他自身static布局的布局,如果不指定位置,则就是static布局。但是好处在于,假如他的父div高度是300,它设置高度100%,则会直接也是300的高度,而不是像static布局那样,自动填充的。关于布局这里不做过多介绍,参考这里自行再了解。

前面提到包裹scroll.vue组件的div一定要设置固定高度,否则组件内部渲染的item高度会自动把父div撑大,也就不存在下拉刷新,上推加载更多的响应事件了。如何根据屏幕大小自动设置div的高度呢?重点就在mounted方法中。下面是getHeight方法的源码:

getHeight(type){
    //获取当前应用所在容器的高度,可以理解为浏览器高度或者屏幕高度,52是我自己要减去的header的高度
    var rh = document.documentElement.clientHeight - 52;
    if (type == 1) {
      return rh;
    }else if(type==2){
      return rh-70;//####3
    }else if(type==3){
      return rh-63-50;//tabBar/筛选(tab高度略)
    }
  }

根据自己的需要调整下里面相关柱子的大小即可。

至此,基于better-scroll的下拉刷新,上推加载更多组件应用就写完了。

猜你喜欢

转载自blog.csdn.net/liangcha007/article/details/84769724