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,用于保存第一次点击的位置,这样处理更加优雅简洁。因为这里只有一个组件,我就写到代码里面。

是不是很简单好用 :)

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,578评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,701评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,691评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,974评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,694评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,026评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,015评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,193评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,719评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,442评论 3 360
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,668评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,151评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,846评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,255评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,592评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,394评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,635评论 2 380

推荐阅读更多精彩内容