scroll-ufo.vue 9.28 KB
<template>
  <div ref="wrapper" class="list-wrapper">
    <div class="scroll-content">
      <div ref="listWrapper">
        <slot></slot>
      </div>
      <slot name="pullup"
        :pullUpLoad="pullUpLoad"
        :isPullUpLoad="isPullUpLoad"
      >
        <div class="pullup-wrapper" v-if="pullUpLoad">
          <div class="before-trigger" v-if="!isPullUpLoad">
            <span>{{pullUpTxt}}</span>
          </div>
          <div class="after-trigger" v-else>
            <span>加载中</span>
          </div>
        </div>
      </slot>
    </div>
    <slot name="pulldown"
      :pullDownRefresh="pullDownRefresh"
      :pullDownStyle="pullDownStyle"
      :beforePullDown="beforePullDown"
      :bubbleY="bubbleY"
    >
      <div ref="pulldown" class="pulldown-wrapper" :style="pullDownStyle" v-if="pullDownRefresh">
        <div class="before-trigger" v-show="beforePullDown">
          <div id="beforePullDown"></div>
        </div>
        <div class="after-trigger" v-show="!beforePullDown">
          <div class="loading">
            <div id="afterPullDown"></div>
          </div>
        </div>
      </div>
    </slot>
  </div>
</template>

<script>
import {BetterScroll} from 'cube-ui';

function getRect(el) {
  if (el instanceof window.SVGElement) {
    let rect = el.getBoundingClientRect();

    return {
      top: rect.top,
      left: rect.left,
      width: rect.width,
      height: rect.height
    };
  } else {
    return {
      top: el.offsetTop,
      left: el.offsetLeft,
      width: el.offsetWidth,
      height: el.offsetHeight
    };
  }
}


const DIRECTION_H = 'horizontal';
const DIRECTION_V = 'vertical';

export default {
  name: 'ScrollUfo',
  props: {
    data: {
      type: Array,
      default: function() {
        return [];
      }
    },
    probeType: {
      type: Number,
      default: 1
    },
    click: {
      type: Boolean,
      default: true
    },
    listenScroll: {
      type: Boolean,
      default: false
    },
    listenBeforeScroll: {
      type: Boolean,
      default: false
    },
    listenScrollEnd: {
      type: Boolean,
      default: false
    },
    direction: {
      type: String,
      default: DIRECTION_V
    },
    scrollbar: {
      type: null,
      default: false
    },
    pullDownRefresh: {
      type: null,
      default() {
        return {
          threshold: 70,
          stop: 90
        };
      }
    },
    pullUpLoad: {
      type: null,
      default: false
    },
    startY: {
      type: Number,
      default: 0
    },
    refreshDelay: {
      type: Number,
      default: 20
    },
    freeScroll: {
      type: Boolean,
      default: false
    },
    mouseWheel: {
      type: Boolean,
      default: false
    },
    bounce: {
      default: true
    },
    zoom: {
      default: false
    },
    observeDom: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      beforePullDown: true,
      isRebounding: false,
      isPullUpLoad: false,
      isPullingDown: false,
      pullUpDirty: true,
      pullDownStyle: '',
      bubbleY: 0
    };
  },
  computed: {
    pullUpTxt() {
      const moreTxt = (this.pullUpLoad && this.pullUpLoad.txt && this.pullUpLoad.txt.more) || '下拉加载';
      const noMoreTxt = (this.pullUpLoad && this.pullUpLoad.txt && this.pullUpLoad.txt.noMore) || '没有更多了';

      return this.pullUpDirty ? moreTxt : noMoreTxt;
    },
    refreshTxt() {
      return (this.pullDownRefresh && this.pullDownRefresh.txt) || '加载完成';
    }
  },
  created() {
    this.pullDownInitTop = -90;
  },
  mounted() {
    setTimeout(() => {
      this.initScroll();
    }, 20);
  },
  methods: {
    initScroll() {
      if (!this.$refs.wrapper) {
        return;
      }
      if (this.$refs.listWrapper && (this.pullDownRefresh || this.pullUpLoad)) {
        this.$refs.listWrapper.style.minHeight = `${getRect(this.$refs.wrapper).height + 1}px`;
      }
      let options = {
        probeType: this.probeType,
        click: this.click,
        scrollY: this.freeScroll || this.direction === DIRECTION_V,
        scrollX: this.freeScroll || this.direction === DIRECTION_H,
        scrollbar: this.scrollbar,
        pullDownRefresh: this.pullDownRefresh,
        pullUpLoad: this.pullUpLoad,
        startY: this.startY,
        freeScroll: this.freeScroll,
        mouseWheel: this.mouseWheel,
        bounce: this.bounce,
        zoom: this.zoom,
        observeDOM: this.observeDom,
      };

      this.scroll = new BetterScroll(this.$refs.wrapper, options);

      if (this.listenScrollEnd) {
        this.scroll.on('scrollEnd', (pos) => {
          this.$emit('scroll-end', pos);
        });
      }
      if (this.listenBeforeScroll) {
        this.scroll.on('beforeScrollStart', () => {
          this.$emit('beforeScrollStart');
        });
        this.scroll.on('scrollStart', () => {
          this.$emit('scroll-start');
        });
      }
      if (this.pullDownRefresh) {
        this._initPullDownRefresh();
      }
      if (this.pullUpLoad) {
        this._initPullUpLoad();
      }
      this._initLottie();
    },
    disable() {
      this.scroll && this.scroll.disable();
    },
    enable() {
      this.scroll && this.scroll.enable();
    },
    refresh() {
      this.scroll && this.scroll.refresh();
    },
    scrollTo() {
      this.scroll && this.scroll.scrollTo.apply(this.scroll, arguments);
    },
    scrollToElement() {
      this.scroll && this.scroll.scrollToElement.apply(this.scroll, arguments);
    },
    destroy() {
      this.scroll.destroy();
    },
    forceUpdate(dirty) {
      if (this.pullDownRefresh && this.isPullingDown) {
        this.isPullingDown = false;
        this._reboundPullDown().then(() => {
          this._afterPullDown();
        });
      } else if (this.pullUpLoad && this.isPullUpLoad) {
        this.isPullUpLoad = false;
        this.scroll.finishPullUp();
        this.pullUpDirty = dirty;
        this.refresh();
      } else {
        this.refresh();
      }
    },
    _initLottie() {
      setTimeout(() => {
        import(/* webpackChunkName: "lottie-web" */ 'lottie-web').then(lottie => {
          this.lottieBefore = lottie.loadAnimation({
            container: document.getElementById('beforePullDown'), // the dom element that will contain the animation
            renderer: 'svg',
            loop: false,
            autoplay: true,
            path: 'https://cdn.yoho.cn/mapp/lottie/ufo-pull-1227.json' // the path to the animation json
          });
          this.lottieAfter = lottie.loadAnimation({
            container: document.getElementById('afterPullDown'), // the dom element that will contain the animation
            renderer: 'svg',
            loop: true,
            autoplay: true,
            path: 'https://cdn.yoho.cn/mapp/lottie/ufo-refresh-1227.json' // the path to the animation json
          });
          this.$nextTick(() => {
            this.pullDownInitTop = 0 - this.$refs.pulldown.clientHeight;
          });
        });
      }, 200);
    },
    _initPullDownRefresh() {
      this.scroll.on('pullingDown', () => {
        this.beforePullDown = false;
        this.isPullingDown = true;
        this.$emit('pullingDown');
      });
      this.scroll.on('scroll', (pos) => {
        if (!this.pullDownRefresh) {
          return;
        }
        if (this.beforePullDown) {
          if (pos.y > 43) {
            this.lottieBefore.goToAndStop(Math.abs(pos.y - 43) * 15);
          } else {
            this.lottieBefore.goToAndStop(0);
          }
        }
        if (pos.y >= this.pullDownInitTop) {
          this.pullDownStyle = `top:${Math.min(pos.y + this.pullDownInitTop, 0)}px`;
          if (this.isRebounding) {
            this.pullDownStyle = `top:${0 - (this.pullDownRefresh.stop || 40 - pos.y)}px`;
          }
        }
      });
    },
    _initPullUpLoad() {
      this.scroll.on('pullingUp', () => {
        this.isPullUpLoad = true;
        this.$emit('pullingUp');
      });
    },
    _reboundPullDown() {
      const {stopTime = 600} = this.pullDownRefresh;

      return new Promise((resolve) => {
        setTimeout(() => {
          this.isRebounding = true;
          this.scroll.finishPullDown();
          resolve();
        }, stopTime);
      });
    },
    _afterPullDown() {
      setTimeout(() => {
        this.pullDownStyle = `top:${this.pullDownInitTop}px`;
        this.beforePullDown = true;
        this.isRebounding = false;
        this.refresh();
      }, this.scroll.options.bounceTime);
    }
  },
  watch: {
    data() {
      setTimeout(() => {
        this.refresh();
      }, this.refreshDelay);
    }
  }
};
</script>

<style lang="scss" scoped>
.list-wrapper {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  bottom: 0;
  overflow: hidden;
  background: #fff;

  .scroll-content {
    position: relative;
    z-index: 1;
  }

  .list-content {
    position: relative;
    z-index: 10;
    background: #fff;

    .list-item {
      height: 60px;
      line-height: 60px;
      font-size: 18px;
      padding-left: 20px;
      border-bottom: 1px solid #e5e5e5;
    }
  }

  .pulldown-wrapper {
    position: absolute;
    left: 60px;
    right: 60px;
    display: flex;
    justify-content: center;
    align-items: center;
    transition: all;
    width: 630px;
    height: 168px;
  }

  .pullup-wrapper {
    width: 100%;
    justify-content: center;
    align-items: center;
    display: flex;
    padding: 16px 0;
  }
}
</style>