VUE 版本 PullDownRefresh 組件

學(xué)習(xí)了有贊pullRefresh的組件宣旱,自己寫了一個(gè)下拉刷新的組件霎肯。上碼:

<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,
     // 記入第一次點(diǎn)擊的位置
      startTouch: {}
    }
  },
  computed: {
    trackStyle () {
      return {
        transitionDuration: `${this.state.duration}ms`,
        transform: this.state.distance
          ? `translate3d(0,${this.state.distance}px, 0)`
          : ''
      }
    }
  },
  watch: {  // 監(jiān)聽外部傳參的變化
    value (val) {
      if (val) {
        this.setStatus(this.headHeight, true)
      } else {
        this.setStatus(0, false)
      }
    }
  },
  methods: {
    showStatusText () {
      return TEXT_STATUS.indexOf(this.state.status) !== -1
    },
    // 是否可以觸發(fā)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">
         這里寫你的頁面內(nèi)容
      </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,用于保存第一次點(diǎn)擊的位置羞反,這樣處理更加優(yōu)雅簡(jiǎn)潔布朦。因?yàn)檫@里只有一個(gè)組件,我就寫到代碼里面昼窗。

是不是很簡(jiǎn)單好用 :)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末是趴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子澄惊,更是在濱河造成了極大的恐慌唆途,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掸驱,死亡現(xiàn)場(chǎng)離奇詭異肛搬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)亭敢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門滚婉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人帅刀,你說我怎么就攤上這事让腹。” “怎么了扣溺?”我有些...
    開封第一講書人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵骇窍,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我锥余,道長(zhǎng)腹纳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任驱犹,我火速辦了婚禮嘲恍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘雄驹。我一直安慰自己佃牛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開白布医舆。 她就那樣靜靜地躺著俘侠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蔬将。 梳的紋絲不亂的頭發(fā)上爷速,一...
    開封第一講書人閱讀 52,807評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音霞怀,去河邊找鬼惫东。 笑死,一個(gè)胖子當(dāng)著我的面吹牛毙石,可吹牛的內(nèi)容都是我干的凿蒜。 我是一名探鬼主播禁谦,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼废封!你這毒婦竟也來了州泊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤漂洋,失蹤者是張志新(化名)和其女友劉穎遥皂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刽漂,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡演训,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贝咙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片样悟。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖庭猩,靈堂內(nèi)的尸體忽然破棺而出窟她,到底是詐尸還是另有隱情,我是刑警寧澤蔼水,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布震糖,位于F島的核電站,受9級(jí)特大地震影響趴腋,放射性物質(zhì)發(fā)生泄漏吊说。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一优炬、第九天 我趴在偏房一處隱蔽的房頂上張望颁井。 院中可真熱鬧,春花似錦蠢护、人聲如沸雅宾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至单寂,卻和暖如春贬芥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宣决。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工蘸劈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人尊沸。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓威沫,卻偏偏與公主長(zhǎng)得像贤惯,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子棒掠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容