VUE 版本 PullDownRefresh 组件

学习了有赞pullRefresh的组件,自己写了一个下拉刷新的组件。上码:

<template>
  <div class="van-pull-down-refresh">
    <div
      ref="pullDownRefresh"
      class="my_track"
      :style="trackStyle"
      @touchstart="onTouchStart"
      @touchmove="onTouchMove"
      @touchend="onTouchEnd"
      @touchcancel="onTouchEnd"
    >
      <slot></slot>
      <div class="my_bottom">
        <div v-if="showStatusText" class="text"> {{ this.getStatusText() }} </div>
        <van-loading size="16" > {{ this.getStatusText() }} </van-loading>
      </div>
    </div>
  </div>
</template>

<script>
const MIN_DISTANCE = 10
const DEFAULT_BOTTOM_HEIGHT = 50
const TEXT_STATUS = ['pulling', 'loosing', 'success']
export default {
  props: {
    disabled: {
      type: Boolean,
      default: () => []
    },
    successText: String,
    pullingText: String,
    loosingText: String,
    loadingText: String,
    value: {
      type: Boolean,
      default: false
    }
  },
  data () {
    return {
      successDuration: 500,
      animationDuration: 300,
      // 下拉的最大高度
      headHeight: DEFAULT_BOTTOM_HEIGHT,
      state: {
        status: 'normal',
        distance: 0,
        duration: 0
      },
      reachBottom: null,
     // 记入第一次点击的位置
      startTouch: {}
    }
  },
  computed: {
    trackStyle () {
      return {
        transitionDuration: `${this.state.duration}ms`,
        transform: this.state.distance
          ? `translate3d(0,${this.state.distance}px, 0)`
          : ''
      }
    }
  },
  watch: {  // 监听外部传参的变化
    value (val) {
      if (val) {
        this.setStatus(this.headHeight, true)
      } else {
        this.setStatus(0, false)
      }
    }
  },
  methods: {
    showStatusText () {
      return TEXT_STATUS.indexOf(this.state.status) !== -1
    },
    // 是否可以触发touch事件
    isTouchable () {
      return this.state.status !== 'loading' && this.state.status !== 'success' && !this.disabled
    },
    ease (dis) {
      const headHeight = this.headHeight
      let distance = Math.abs(dis)
      if (distance > headHeight) {
        if (distance < headHeight * 2) {
          distance = headHeight + (distance - headHeight) / 2
        } else {
          distance = headHeight * 1.5 + (distance - headHeight * 2) / 4
        }
      }
      return -Math.round(distance)
    },
    setStatus (distance, isLoading) {
      this.state.distance = distance
      if (isLoading) {
        this.state.status = 'loading'
      } else if (distance === 0) {
        this.state.status = 'normal'
      } else if (Math.abs(distance) < this.headHeight) {
        this.state.status = 'pulling'
      } else {
        this.state.status = 'loosing'
      }
    },
    getStatusText () {
      const { status } = this.state
      if (status === 'normal') {
        return ''
      }
      return this[`${status}Text`]
    },
    getRootOffsetHeight () {
      return (
        window.innerHeight ||
        document.documentElement.offsetHeight ||
        document.body.offsetHeight ||
        0
      )
    },
    checkPosition (event) {
      this.reachBottom = this.$refs.pullDownRefresh.getBoundingClientRect().bottom < this.getRootOffsetHeight()
      if (this.reachBottom) {
        this.state.duration = 0
        this.startTouch = event.touches[0]
      }
    },
    onTouchStart (event) {
      if (this.isTouchable()) {
        this.checkPosition(event)
      }
    },
    getDirection (x, y) {
      if (x > y && x > MIN_DISTANCE) {
        return 'horizontal'
      }
      if (y > x && y > MIN_DISTANCE) {
        return 'vertical'
      }
      return ''
    },
    onTouchMove (event) {
      if (this.isTouchable() && this.startTouch.clientY) {
        if (!this.reachBottom) {
          this.checkPosition(event)
        }
        const deltaX = event.touches[0].clientX - this.startTouch.clientX
        const deltaY = event.touches[0].clientY - this.startTouch.clientY
        const offsetX = Math.abs(deltaX)
        const offsetY = Math.abs(deltaY)
        this.deltaY = deltaY
        if (this.reachBottom && deltaY <= 0 && this.getDirection(offsetX, offsetY) === 'vertical') {
          if (event.cancelable) {
            event.preventDefault()
          }
          this.setStatus(this.ease(deltaY))
        }
      }
    },
    onTouchEnd () {
      if (this.reachBottom && this.deltaY && this.isTouchable()) {
        this.state.duration = this.animationDuration
        if (this.state.status === 'loosing') {
          this.setStatus(this.headHeight, true)
          this.$emit('update:value', true)
          // ensure value change can be watched
          this.$nextTick(() => {
            this.$emit('refresh')
          })
        } else {
          this.setStatus(0)
        }
      }
    }
  }
  
}
</script>

<style lang="less" scoped>
  .van-pull-down-refresh {
    overflow: hidden;
    user-select: none;

    .my_track {
      position: relative;
      height: 100%;
      transition-property: transform;
    }

    .my_bottom {
      position: absolute;
      left: 0;
      width: 100%;
      height: 50px;
      overflow: hidden;
      font-size: 14px;
      line-height: 50px;
      color: #969799;
      text-align: center;
      transform: translateY(0%);
    }
  }
</style>

使用方法:

<template>
  <pull-down-refresh
      v-model="isLoading"
      @refresh="onRefresh"
      pullingText="上拉即可更新"
      loosingText="释放即可更新"
      loadingText="更新中"
      :disabled="isFinished"
    >
      <div class="list-box">
         这里写你的页面内容
      </div>
  </pull-down-refresh>
</template>

<script>
import PullDownRefresh from '@/components/PullDownRefresh'
export default {
  name: 'xxx',
  components: {
    PullDownRefresh
  },
  data () {
    return {
      isLoading: false,
      isFinished: false
    }
  },
  methods: {
    onRefresh () { ... }
  }
}
</script>

备注: 有赞vant的组件中把touch事件的处理放到了工具文件scroll.js,并且用了vue3的新功能ref,用于保存第一次点击的位置,这样处理更加优雅简洁。因为这里只有一个组件,我就写到代码里面。

是不是很简单好用 :)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容