基於IScroll插件的vue自定義滾動組件

<template>
  <div id="wrapper" ref="wrapper" :class="className">
    <div class="scroller" :class="{'scroll-static': showIcon && !scrolling, 'no-scroll': !up && !down}" ref="scroller">
      <div class="scroll-head-mask" v-if="up" :style="headbg"></div>
      <div v-if="up" :style="textColor" class="scroll-head tc">
        <p v-show="showBeforeText">鬆開刷新</p>
        <p :class="{'no-fixation': !shadowIcon}" >
          <span class="shadow-icon" :style="shadowColor"></span>
          <span class="pull-icon" ref="pullIcon"></span>
        </p>
        <span class="size10">上次更新時間: {{ headText }}</span>
      </div>
      <slot>
        <!-- <div class="tc">暫無數據</div> -->
      </slot>
      <div class="scroll-foot" v-if="down">
        <div v-show="!allLoaded">{{ footText }}</div>
        <div v-show="allLoaded">全部加載完成</div>
      </div>
    </div>
  </div>
</template>
<script>
/**
 * @author liyulin
 * @description 下拉刷新,上拉加載組件
 * @date 2018-7-4
 * @param value Object 控制關閉刷新,IScroll重新計算滾動高度
 * @param up Boolean 是否需要下拉刷新
 * @param down Boolean 是否需要上拉刷新
 * @param allLoaded Boolean 是否全部加載完畢
 * @param refreshColor Object 自定義頭部樣式
 * @param className String wrapper類名
 * @param getScrilling Boolean 滾動時是否需要scroll實例信息
 * @event refresh 下拉刷新事件
 * @event getMore 上拉加載更多
 * @method scrollIntoView 使內容滾動到視圖區域,同原生srollIntoView,默認參數true
 */
import IScroll from '@/assets/js/iscroll-probe'
import { formatDate } from '@/assets/js/utils'
export default {
  name: 'NfScroll',
  props: {
    value: {
      type: [Boolean, Number],
      default: true
    },
    up: {
      type: [Boolean, Number],
      default: false
    },
    down: {
      type: [Boolean, Number],
      default: false
    },
    allLoaded: {
      type: [Boolean, Number],
      default: false
    },
    refreshColor: {
      type: Object,
      default() {
        return {
          loadBgColor: '#F5F5F5', // 背景色
          textColor: '#666', // 文本顏色
          cirCleColor: '#d2d2d2' // 跳躍陰影顏色
        }
      }
    },
    className: String,
    getScrilling: Boolean
  },
  data() {
    return {
      myScroll: null, // IScroll實例
      observer: null, // 觀察者實例
      headText: formatDate(new Date(), 'yyyy-MM-dd hh:mm:ss'), // 頭部提示
      footText: '上拉加載更多', // 底部提示
      timer: 0, // 刷新時請求的計時器
      minTimer: 1000, // 刷新最少延時
      maxTimer: 29000, // 刷新最大延時(實際是30秒)
      showIcon: false, // 下拉刷新動畫圖標
      scrolling: false, // 滾動時
      showBeforeText: false, // 提示文字
      shadowIcon: true, // 陰影圖標
      recordOldHeight: '0' // 記錄下舊的高度,避免重複觸發回調函數
    }
  },
  computed: {
    // 計算頭部樣式
    headbg() {
      const r = this.refreshColor && this.refreshColor.loadBgColor
      return r
        ? `background-color: ${this.refreshColor.loadBgColor}`
        : 'background-color: #F5F5F5'
    },
    // 頭部文字樣式
    textColor() {
      var textColor = '#666'
      if (this.refreshColor && this.refreshColor.textColor) {
        if (this.refreshColor.textColor) {
          textColor = this.refreshColor.textColor
        }
      }
      textColor = 'color:' + textColor + ';' + this.headbg
      return textColor
    },
    // icon樣式
    shadowColor() {
      let bc = ''
      const jumpBc = this.CONSTS.jumpBcColor
      this.refreshColor &&
        this.refreshColor.cirCleColor &&
        (bc = this.refreshColor.cirCleColor)
      !this.showIcon && (bc = jumpBc === bc ? '#062db2' : '#999')
      return `background-color: ${bc}`
    }
  },
  watch: {
    value(a) {
      if (!a) {
        const _this = this
        let timer = this.minTimer - (+new Date() - +new Date(this.timer))
        timer = timer > 0 ? timer : 800
        setTimeout(() => {
          _this.headText = formatDate(new Date(), 'yyyy-MM-dd hh:mm:ss')
          _this.footText = '上拉加載更多'
          _this.$store.commit('SET_LOADING_STATE', '1')
          _this.shadowIcon = true
          setTimeout(() => {
            _this.showIcon = a
          }, 600)
        }, timer)
      }
    }
  },
  mounted() {
    this.initLoad()
    this.mutaionObserver()
  },
  beforeDestroy() {
    this.myScroll && this.myScroll.destroy && this.myScroll.destroy()
    this.myScroll = null
    if (this.observer) {
      this.observer.disconnect()
      this.observer.takeRecords()
      this.observer = null
    }
  },
  methods: {
    mutaionObserver() {
      const MObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
      if (!MObserver) {
        console.warn('此設備暫不支持MutationObserver屬性')
        return
      }
      const scroller = this.$refs.scroller
      // 低版本(如android4.3及以下)無MutationObserver屬性時,節點的添加刪除,需要刷新isroll的頁面,需自行處理
      this.observer = new MObserver(mutationList => {
        const height = getComputedStyle(scroller).getPropertyValue('height')
        if (height !== this.recordOldHeight) {
          this.recordOldHeight = height
          this.myScroll.refresh()
        }
      })
      this.observer.observe(scroller, {
        childList: true,
        subtree: true,
        attributeFilter: ['client-height']
      })
    },
    initLoad() {
      let that = this
      const options = {
        mouseWheel: true, // 是否支持鼠標滾輪
        scrollX: false, // 是否顯示橫向滾動條
        bounce: this.up || this.down, // 是否彈跳效果
        probeType: 2, // 爲2的時候,會在屏幕滑動的過程中實時的派發scroll,爲3,實時的派發scroll事件,可能會卡頓
        preventDefault: false // 不阻止默認事件,解決ios點擊移動不觸發事件
      }
      // 兼容andirod系統內核也可以滾動
      if (this.CONSTS.isAndroidSys) {
        Object.assign(options, {
          preventDefault: true,
          preventDefaultException: {
            tagName: /^(SPAN|I|P|B|UL|LI|H1|H2|DIV|A|INPUT|TEXTAREA|BUTTON|SELECT|IMG)$/
          }
        })
      }
      // 初始化IScroll
      this.myScroll = new IScroll(this.$refs.wrapper, options)
      const wrapper = this.$refs.wrapper
      const scroller = this.$refs.scroller
      const pullIcon = this.$refs.pullIcon
      this.myScroll.on('beforeScrollStart', () => {
        this.myScroll.y === 0 && this.showIcon && this.myScroll._translate(0, 80)
      })
      // 開始滾動
      this.myScroll.on('scrollStart', () => {
        if (!this.myScroll) return
        this.scrolling = true
        this.myScroll.refresh()
      })
      // 滾動時
      this.myScroll.on('scroll', () => {
        if (!this.myScroll) return
        this.getScrilling && this.$emit('getScrollData', this.myScroll)
        const y = this.myScroll.y
        if (that.up && y > 0) {
          if (!this.showIcon && y >= 80) {
            that.showBeforeText = true
          } else {
            that.showBeforeText = false
            const bottom = parseFloat(getComputedStyle(pullIcon).getPropertyValue('bottom'))
            bottom <= 0 && (pullIcon.style.bottom = -20 + (y / 4) + 'px')
          }
        }
        const fh = scroller.clientHeight - wrapper.clientHeight + y
        if (
          that.down && y < 0 && fh < 0 &&
          that.footText === '上拉加載更多' &&
          !that.allLoaded
        ) {
          that.footText = '正在加載...'
          that.$emit('input', true)
          that.$store.commit('SET_LOADING_STATE', '0')
          setTimeout(() => {
            that.$emit('getMore')
          }, 1000)
          that.maxDelayed()
        }
      })
      // 滾動結束
      this.myScroll.on('scrollEnd', () => {
        if (!this.myScroll) return
        this.getScrilling && this.$emit('getScrollData', this.myScroll)
        this.myScroll.y >= 0 && !this.showBeforeText && (this.scrolling = false)
      })
      // 觸摸結束
      this.$refs.wrapper.addEventListener('touchend', () => {
        if (!this.myScroll) return
        if (this.up && this.showBeforeText && !this.showIcon) {
          this.showBeforeText = false
          this.showIcon = true
          this.shadowIcon = false
          this.timer = new Date()
          this.$emit('input', true)
          setTimeout(() => {
            this.$emit('refresh')
            this.$store.commit('SET_LOADING_STATE', '0')
            this.maxDelayed()
          }, 20)
        }
        this.myScroll.y >= 0 && !this.showBeforeText && (this.scrolling = false)
      })
    },
    // 請求最大延時
    maxDelayed() {
      setTimeout(() => {
        this.$emit('input', false)
        this.$store.commit('SET_LOADING_STATE', '1')
      }, this.maxTimer)
    },
    // 滾動內容到視圖區域,外層調用,勿刪
    scrollIntoView(p = true) {
      if (p) {
        this.myScroll.scrollTo(0, 0)
      } else {
        this.$nextTick(() => {
          this.myScroll.refresh()
          this.myScroll.scrollTo(0, this.myScroll.maxScrollY, 200)
        })
      }
    },
    // 刷新icroll
    refresh() {
      this.$nextTick(() => {
        this.myScroll.refresh()
      })
    }
  }
}
</script>
<style lang="scss" scoped>
#wrapper {
  height: 100%;
  overflow: hidden;
  .scroller {
    max-width: 100%; // 解決第一次安裝,部分頁面可以左右滑動的問題
    width: 100%;
    min-height: calc(100% + 1.5px);
    position: relative;
    display: flex;
    flex-flow: column;
    &.no-scroll {
      min-height: 100%;
    }
    &.scroll-static {
      transform: translate(0px, 80px) translateZ(0px) !important;
      transition-duration: 400ms !important;
      .no-fixation {
        .pull-icon {
          animation: jumps .8s linear infinite;
        }
        .shadow-icon {
          border-radius: 50%;
          animation: jump-b .8s linear infinite;
        }
      }
    }
  }
  .scroll-head {
    position: absolute;
    top: -5rem;
    left: 0;
    right: 0;
    height: 5rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    p {
      width: 100%;
      height: 3.75rem;
      position: relative;
      display: flex;
      justify-content: center;
      overflow: hidden;
      .pull-icon {
        display: inline-block;
        box-sizing: border-box;
        width: 1.625rem;
        height: 1.5625rem;
        background: url(../assets/img/scroll-icon.png) no-repeat center;
        background-size: 1.625rem 1.5625rem;
        position: absolute;
        bottom: -1.25rem;
        animation: jump-one .6s linear 1;
      }
      .shadow-icon {
        display: inline-block;
        width: 1.875rem;
        height: .25rem;
        position: absolute;
        bottom: 0;
      }
    }
  }
  .scroll-head-mask {
    height: 50vh;
    position: absolute;
    top: -50vh;
    left: 0;
    right: 0;
    background-color: #ccc;
  }
  .scroll-foot {
    > div {
      height: 35px;
      text-align: center;
      line-height: 35px;
      color: #bdbdbd;
    }
  }
}
@keyframes jump-one {
  from {
    bottom: 19px;
  }
  to {
    bottom: -1.25rem;
  }
}
@keyframes jumps {
  from {
    bottom: 0px;
  }
  50% {
    bottom: 28px;
  }
  to {
    bottom: 0px;
  }
}
@keyframes jump-b {
  from {
    width: 1.375rem;
  }
  50% {
    width: .8rem;
  }
  to {
    width: 1.375rem;
  }
}
</style>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章