加载中...

VUE实现上拉加载下拉刷新组件,移动端&PC端通用

WuJia
2019-04-29 15:16:14
分类:Vue
141
3
0

其实写这个组件之前是因为在使用某UI组件库时踩了不少坑,到最后还是会出现各种问题,至此就放弃了踩坑,决定自己封装一个类似组件。不敢说自己写的没问题,但是目前运用到项目中各手机设备上基本能通,如果有问题还请各位文下留言,描述下所出现的问题,我这边会第一时间做出优化。

下面我也废话不多说,直接贴出代码:

<style lang="scss">
.loadmore {
  overflow-y: auto;
  position: fixed;
  height: 100%;
  width: 100%;
  left: 0;
  top: 0;
  bottom: 0;
  right: 0;
  z-index: 10;
  user-select: none;
  .loading, .pull, .drop {
    position: fixed;
    left: 0;
    right: 0;
    text-align: center;
    line-height: 30px;
    font-size: 14px;
    z-index: -1;
  }
  .loadmore-content {
    -webkit-backface-visibility: hidden;
    -webkit-transform-style: preserve-3d;
    background: #f5f5f5;
  }
  .top-loading-text {
    top: 0;
  }
  .bottom-loading-text {
    bottom: 0;
  }
}
</style>

<template>
  <section class="loadmore" ref="loadmore">
    <section class="loading top-loading-text" v-if="topLoading" v-html="topLoadingText"></section>
    <section class="pull" :style="{top: `20px`}" v-if="topPull" v-html="topPullText"></section>
    <section class="drop" :style="{top: `20px`}" v-if="topDrop" v-html="topDropText"></section>
    <section class="loadmore-content" ref="list" :style="style">
      <slot></slot>
    </section>
    <section v-if="bottomDrop" class="drop" :style="{bottom: `20px`}" v-html="bottomDropText"></section>
    <section v-if="bottomPull" class="pull" :style="{bottom: `20px`}" v-html="bottomPullText"></section>
    <section v-if="bottomLoading" class="loading bottom-loading-text" v-html="bottomLoadingText"></section>
  </section>
</template>

<script>
export default {
  name: 'loadmore',
  data () {
    return {
      style: {},
      direction: '',
      touchstart: 'touchstart',
      touchmove: 'touchmove',
      touchend: 'touchend',
      loadmore: null,
      list: null,
      dY: 0,
      mY: 0,
      bottomLoading: false,
      bottomPull: false,
      bottomDrop: false,
      topLoading: false,
      topPull: false,
      topDrop: false,
      topOn: false,
      bottomOn: false,
      scrollOn: true
    }
  },
  computed: {
    isMobile () {
      return /Mobile/ig.test(window.navigator.userAgent)
    }
  },
  props: {
    //提前触发到底事件的高度,默认到底触发(手机端建议默认此参数)
    earlyTrigger: {
      type: Number,
      validater (v) {
        return v >= 0
      },
      default () {
        return 0
      }
    },
    //手指移动距离和组件滚动之间的比率
    distanceIndex: {
      type: Number,
      default () {
        return 3
      }
    },
    //更新完,设置此参数不再触发 topMethod 方法
    topAllLoaded: {
      type: Boolean,
      default () {
        return true
      }
    },
    //下拉刷新的高度
    topDistance: {
      type: Number,
      validater (v) {
        return v > 30
      },
      default () {
        return 60
      }
    },
    //上拉加载的高度
    bottomDistance: {
      type: Number,
      validater (v) {
        return v > 30
      },
      default () {
        return 60
      }
    },
    //加载完,设置此参数不再触发 bottomMethod 方法
    bottomAllLoaded: {
      type: Boolean,
      default () {
        return false
      }
    },
    //是否自动填充内容
    autoFill: {
      type: Boolean,
      default () {
        return true
      }
    },
    //下拉刷新提示内容,可传入HTML
    topPullText: {
      type: String,
      default () {
        return '↓ 下拉刷新'
      }
    },
    //下拉刷新到达指定高度提示内容,可传入HTML
    topDropText: {
      type: String,
      default () {
        return '释放更新'
      }
    },
    //下拉更新中提示内容,可传入HTML
    topLoadingText: {
      tye: String,
      default () {
        return '更新中...'
      }
    },
    //上拉加载提示内容,可传入HTML
    bottomPullText: {
      type: String,
      default () {
        return '↑ 上拉加载'
      }
    },
    //上拉加载到达指定高度提示内容,可传入HTML
    bottomDropText: {
      type: String,
      default () {
        return '释放更新'
      }
    },
    //上拉加载中提示内容,可传入HTML
    bottomLoadingText: {
      type: String,
      default () {
        return '加载中...'
      }
    },
    //下拉刷新,触发方法
    topMethod: {
      type: Function,
      default: () => {}
    },
    //上拉加载,触发方法
    bottomMethod: {
      type: Function,
      default: () => {}
    }
  },
  methods: {
    //偏移元素
    deviationElement (distance, isAnimation) {
      this.style = isAnimation && {
        transform: `translate3d(0, ${ distance }px, 0)`,
        transition: 'transform .3s ease'
      } || {
        transform: `translate3d(0, ${ distance }px, 0)`
      }
    },
    //底部加载完成后调用,关闭加载状态
    onBottomLoadedSuccess () {
      this.bottomLoading = this.bottomDrop = this.bottomPull = false
      this.deviationElement(0, true)
    },
    //顶部加载完成后调用,关闭加载状态
    onTopLoadedSuccess () {
      this.topLoading = this.topDrop = this.topPull = false
      this.deviationElement(0, true)
    },
    //手指(鼠标按键)抬起
    removeEv () {
      //上拉释放
      if (this.direction == 'up') {
        if (this.bottomDrop) {
          this.deviationElement(-30, true)
          setTimeout(() => {
            this.bottomDrop = this.bottomPull = false
            this.bottomLoading = true
            this.bottomMethod()
          }, 300)
        } else {
          this.deviationElement(0, true)
          setTimeout(() => {
            this.bottomLoading = this.bottomDrop = this.bottomPull = false
          }, 300)
        }
      } else if (this.direction == 'down') {//下拉释放
        if (this.topDrop) {
          this.deviationElement(30, true)
          setTimeout(() => {
            this.topDrop = this.topPull = false
            this.topLoading = true
            this.topMethod()
          }, 300)
        } else {
          this.deviationElement(0, true)
          setTimeout(() => {
            this.topLoading = this.topDrop = this.topPull = false
          }, 300)
        }
      }
      this.loadmore.removeEventListener(this.touchmove, this.moveEv)
      this.loadmore.removeEventListener(this.touchend, this.removeEv)
    },
    //手指(鼠标)按下事件
    startEv (e) {
      e = e || window.event
      this.dY = this.isMobile && e.touches[0].pageY || e.pageY
      this.topOn = this.bottomOn = true
      this.loadmore.addEventListener(this.touchmove, this.moveEv)
      this.loadmore.addEventListener(this.touchend, this.removeEv)
    },
    //手指(鼠标按住拖动)滑动事件
    moveEv (ev) {
      ev = ev || window.event
      this.mY = this.isMobile && ev.touches[0].pageY || ev.pageY
      let sT = this.loadmore.scrollTop,
          prevent = false
      //下拉更新
      if ((this.mY - this.dY) > 0) {
        this.direction = 'down'
        if (sT <= 0) {
          prevent = true
          if (this.topOn) {
            this.topOn = false
            this.dY = this.mY
          }

          let top = (this.mY - this.dY) / this.distanceIndex
          this.deviationElement(top)
          if ((top > 0) && (top < this.topDistance) && !this.topAllLoaded) {
            this.topLoading = this.topDrop = false
            this.topPull = true
          } else if ((top > this.topDistance) && !this.topAllLoaded) {
            this.topLoading = this.topPull = false
            this.topDrop = true
          }
        }
      } else {//上拉加载
        this.direction = 'up'
        let h = this.loadmore.clientHeight - this.list.scrollHeight
        if (Math.abs(h) <= sT) {
          prevent = true
          if (this.bottomOn) {
            this.bottomOn = false
            this.dY = this.mY
          }

          let bottom = (this.mY - this.dY) / this.distanceIndex,
              absBottom = Math.abs(bottom)
          this.deviationElement(bottom)
          if ((absBottom > 0) && (absBottom < this.bottomDistance) && !this.bottomAllLoaded) {
            this.bottomLoading = this.bottomDrop = false
            this.bottomPull = true
          } else if ((absBottom > this.bottomDistance) && !this.bottomAllLoaded) {
            this.bottomLoading = this.bottomPull = false
            this.bottomDrop = true
          }
        }
      }

      prevent && ev.preventDefault()
    },
    //scorll 监听事件
    scrollEv (e) {
      let sH = this.list.scrollHeight,
          cH = this.loadmore.clientHeight,
          top = this.loadmore.scrollTop
      if ((sH - cH - this.earlyTrigger) <= top && this.scrollOn) {
        this.scrollOn = false
        this.$emit('bottomStatusChange', '到底啦')
      }
    }
  },
  mounted () {
    this.$nextTick(() => {
      if (!this.isMobile) {
        this.touchstart = 'mousedown'
        this.touchmove = 'mousemove'
        this.touchend = 'mouseup'
      }
      this.loadmore = this.$refs.loadmore
      this.list = this.$refs.list
      //自动填充
      if ((this.loadmore.clientHeight > this.list.scrollHeight) && this.autoFill) {
        this.bottomMethod()
      }

      !this.earlyTrigger && this.loadmore.addEventListener(this.touchstart, this.startEv)
      this.earlyTrigger && this.loadmore.addEventListener('scroll', this.scrollEv)
    })
  },
  deactivated () {
    !this.earlyTrigger && this.loadmore.removeEventListener(this.touchstart, this.startEv)
  }
}
</script>

此功能实现起来其实并不复杂,相信各位也都能看的懂,然后就上方代码我这边说说如何实现到头或者到尾再往下拉的弹性效果。

(手指移动的)pageY- (手指按下的)pageY / 3 (系数)

上面这个公式的意思是指,当前手指在屏幕上从按下位置到移动结束位置的值,然后除以3,取当前这个值的三分之一来用做赋值移动元素位置,这样就给人感觉元素越来越难拉动一样的效果。
代码可能写的不是很好,各位不要喷我,我也只是一个在努力前进的搬砖人。
这里我就不介绍如何使用组件了,详细使用方式请移步github:
https://github.com/wujiabk/vue-loadmore

3

发表评论(共0条评论)

请输入评论内容
啊哦,暂无评论数据~