Loadmore

Loadmore使用的時候分爲下拉刷新和底部加載兩種方式。css

下拉刷新的時候這樣調用:html

<template>
  <div class="page-loadmore">
    <h1 class="page-title">Pull down</h1>
    <p class="page-loadmore-desc">在列表頂端, 按住 - 下拉 - 釋放能夠獲取更多數據</p>
    <p class="page-loadmore-desc">此例請使用手機查看</p>
    <p class="page-loadmore-desc">translate : {{ translate }}</p>

    <div class="loading-background" :style="{ transform: 'scale3d(' + moveTranslate + ',' + moveTranslate + ',1)' }">
      translateScale : {{ moveTranslate }} 
    </div>
    <div class="page-loadmore-wrapper" ref="wrapper" :style="{ height: wrapperHeight + 'px' }">
      <!-- page-loadmore-wrapper元素是loadmore模塊的父級盒子,它的高度是綁定了一個響應式的值wrapperHeight -->
      <!-- 在生命週期mounted的時候爲page-loadmore-wrapper計算高度 -->
      <!-- page-loadmore-wrapper有一個ref屬性,這就是給這個DOM元素添加了一個引用,在當前組件裏能夠用this.$refs的形式來調用這個DOM元素 -->
      <loadmore :top-method="loadTop" @translate-change="translateChange" @top-status-change="handleTopChange" ref="loadmore">
        <!-- loadmore組件,傳進去了一個屬性,loadTop會從props接收到 -->
        <!-- loadTop方法用於給列表添加數據項 -->
        <!-- 還給loadmore組件綁定了自定義事件top-status-change,用於更改topStatus這個屬性值 -->
        <!-- top-status-change的觸發是在loadmore組件內部判斷觸發的,子組件$emit觸發 -->
        <ul class="page-loadmore-list">
          <li v-for="(item, key, index) in list" :key="index" class="page-loadmore-listitem">{{ item }}</li>
        </ul>
        <!-- page-loadmore-list是數據列表 -->
        <div slot="top" class="mint-loadmore-top">
          <span v-show="topStatus !== 'loading'" :class="{ 'is-rotate': topStatus === 'drop' }">↓</span>
          <span v-show="topStatus === 'loading'">
            <a>加載中...</a>
          </span>
        </div>
        <!-- top插槽插入的內容是下拉的時候,數據列表下移後上面出現的箭頭和loading文字或者動畫 -->
        <!-- 箭頭和文字都隨着topStatus值來改變顯示狀態和樣式 -->
        <!-- topStatus有三種狀態:pull,drop,loading -->
        <!-- loading的時候顯示文字或者動畫,其它時候顯示箭頭 -->
      </loadmore>
    </div>
  </div>
</template>

<style lang="scss" scoped>
  .page-loadmore {
    width: 100%;
    overflow-x: hidden;
    .page-loadmore-wrapper {
      margin-top: -1px;
      overflow: scroll;
      .page-loadmore-listitem {
        height: 50px;
        line-height: 50px;
        border-bottom: solid 1px #eee;
        text-align: center;
        &:first-child {
          border-top: solid 1px #eee;
        }
      }
    }
    .loading-background {
      width: 100%;
      height: 50px;
      line-height: 50px;
      text-align: center;
      transition: .2s linear;
    }
    .mint-loadmore-top {
      span {
        display: inline-block;
        transition: .2s linear;
        vertical-align: middle;
      }
      .is-rotate {
        transform: rotate(180deg);
      }
    }
  }
</style>

<script type="text/babel">
  import loadmore from '@/components/loadmore'
  export default {
    data() {
      return {
        list: [],//數據列表
        topStatus: '',//上方loading層狀態
        wrapperHeight: 0,//包裹盒子高度
        translate: 0,
        moveTranslate: 0
      };
    },
    methods: {
      handleTopChange(status) {//改變topStatus狀態,下方的箭頭和加載文字會隨着topStatus改變樣式或者內容
        this.moveTranslate = 1;
        this.topStatus = status;
      },
      translateChange(translate) {//loadmore組件在滑動時會觸發此事件運行此方法
        const translateNum = +translate;
        this.translate = translateNum.toFixed(2);
        this.moveTranslate = (1 + translateNum / 70).toFixed(2);
      },
      loadTop() {//加載更多數據列表
        setTimeout(() => {
          let firstValue = this.list[0];
          for (let i = 1; i <= 10; i++) {
            this.list.unshift(firstValue - i);
          }
          this.$refs.loadmore.onTopLoaded();//加載完數據以後調用loadmore組件的onTopLoaded方法
        }, 1500);
      }
    },
    components: {
      loadmore
    },
    created() {//created的時候先給數據列表裏填入20條數據
      for (let i = 1; i <= 20; i++) {
        this.list.push(i);
      }
    },
    mounted() {
      this.wrapperHeight = document.documentElement.clientHeight - this.$refs.wrapper.getBoundingClientRect().top;
      //計算page-loadmore-wrapper的高度
      //html元素的clientHeight - page-loadmore-wrapper盒子距離頁面頂部的高度
      //Element.getBoundingClientRect()方法返回元素的大小及其相對於視口的位置
      //也就是除了頁面上面的內容以外,下面整個就是page-loadmore-wrapper盒子,wrapper盒子給一個死高度以後,多給一個overflow:scroll;的樣式,這樣內容就能夠經過滑動看到了
    }
  };
</script>

底部加載的時候這樣調用:node

<template>
  <div class="page-loadmore">
    <h1 class="page-title">Pull up</h1>
    <p class="page-loadmore-desc">在列表底部, 按住 - 上拉 - 釋放能夠獲取更多數據</p>
    <p class="page-loadmore-desc">此例請使用手機查看</p>
    <div class="page-loadmore-wrapper" ref="wrapper" :style="{ height: wrapperHeight + 'px' }">
      <!-- page-loadmore-wrapper元素是loadmore模塊的父級盒子,它的高度是綁定了一個響應式的值wrapperHeight -->
      <!-- 在生命週期mounted的時候爲page-loadmore-wrapper計算高度 -->
      <!-- page-loadmore-wrapper有一個ref屬性,這就是給這個DOM元素添加了一個引用,在當前組件裏能夠用this.$refs的形式來調用這個DOM元素 -->
      <loadmore :bottom-method="loadBottom" @bottom-status-change="handleBottomChange" :bottom-all-loaded="allLoaded" ref="loadmore">
        <!-- loadmore組件,傳進去了兩個屬性,loadmore會從props接收到,loadBottom方法和allLoaded屬性 -->
        <!-- loadBottom方法用於給列表添加數據項,allLoaded是個布爾值,判斷是否數據已經所有加載完了 -->
        <!-- 還給loadmore組件綁定了一個自定義事件bottom-status-change,用於更改bottomStatus這個屬性值 -->
        <!-- bottom-status-change的觸發是在loadmore組件內部判斷觸發的,子組件$emit觸發 -->
        <ul class="page-loadmore-list">
          <li v-for="(item, key, index) in list" :key="index" class="page-loadmore-listitem">{{ item }}</li>
        </ul>
        <!-- page-loadmore-list是數據列表 -->
        <div slot="bottom" class="mint-loadmore-bottom">
          <span v-show="bottomStatus !== 'loading'" :class="{ 'is-rotate': bottomStatus === 'drop' }">↑</span>
          <span v-show="bottomStatus === 'loading'">
            <a>加載中...</a>
          </span>
        </div>
        <!-- bottom插槽插入的內容是上拉的時候,數據列表上移後下面出現的箭頭和loading文字或者動畫 -->
        <!-- 箭頭和文字都隨着bottomStatus值來改變顯示狀態和樣式 -->
        <!-- bottomStatus有三種狀態:pull,drop,loading -->
        <!-- loading的時候顯示文字或者動畫,其它時候顯示箭頭 -->
      </loadmore>
      <!-- loadmore組件有三個插槽,top,bottom和默認插槽 -->
      <!-- top是列表下移後上方出現的箭頭和loading文字,bottom是上移後下方出現的箭頭和文字,默認插槽就是數據列表 -->
    </div>
  </div>
</template>

<style lang="scss" scoped>
    .page-loadmore-listitem {
      height: 50px;
      line-height: 50px;
      border-bottom: solid 1px #eee;
      text-align: center;
    }
    .page-loadmore-wrapper {
        overflow: scroll;
    }
    .mint-loadmore-bottom {
      span {
        display: inline-block;
        transition: .2s linear;
      }
      .is-rotate {
        transform: rotate(180deg);
      }
    }
</style>

<script>
import loadmore from '@/components/loadmore'
export default {
  data () {
    return {
      list: [],//數據列表
      allLoaded: false,//是否所有加載
      bottomStatus: '',//下方loading層狀態
      wrapperHeight: 0//包裹盒子高度
    }
  },
  methods: {
    handleBottomChange(status) {//改變bottomStatus狀態,下方的箭頭和加載文字會隨着bottomStatus改變樣式或者內容
      this.bottomStatus = status;
    },
    loadBottom() {//加載更多數據列表
      setTimeout(() => {
        let lastValue = this.list[this.list.length - 1];
        if (lastValue < 40) {
          for (let i = 1; i <= 10; i++) {
            this.list.push(lastValue + i);
          }
        } else {
          this.allLoaded = true;//數據所有加載完了,就改變allLoaded
        }
        this.$refs.loadmore.onBottomLoaded();//加載完數據以後調用loadmore組件的onBottomLoaded方法
      }, 1500);
    }
  },
  components: {
    loadmore
  },
  created () {//created的時候先給數據列表裏填入20條數據
    for (let i = 0; i <= 20; i++) {
      this.list.push(i)
    }
  },
  mounted () {
    this.wrapperHeight = document.documentElement.clientHeight - this.$refs.wrapper.getBoundingClientRect().top
    //計算page-loadmore-wrapper的高度
    //html元素的clientHeight - page-loadmore-wrapper盒子距離頁面頂部的高度
    //Element.getBoundingClientRect()方法返回元素的大小及其相對於視口的位置
    //也就是除了頁面上面的內容以外,下面整個就是page-loadmore-wrapper盒子,wrapper盒子給一個死高度以後,多給一個overflow:scroll;的樣式,這樣內容就能夠經過滑動看到了
  }
}
</script>

loadmore組件:babel

<template>
  <div class="mint-loadmore">
    <!-- mint-loadmore最外層盒子,有overflow:hidden;的樣式,這樣下方的箭頭文字動畫或者上方的就會隱藏看不到 -->
    <div class="mint-loadmore-content" :class="{ 'is-dropped': topDropped || bottomDropped}" :style="{ 'transform': transform }">
      <!-- content盒子擁有兩個響應式屬性,一個在drop的時候添加is-dropped類名,讓transform變化更流暢,一個是transform樣式,在touchmove的時候,會改變盒子在垂直方向的位置 -->
      <!-- transfrom樣式的值是一個計算屬性,會隨着this.translate變化而變化 -->
      <slot name="top">
        <div class="mint-loadmore-top" v-if="topMethod">
          <span v-if="topStatus === 'loading'" class="mint-loadmore-spinner"></span>
          <span class="mint-loadmore-text">{{ topText }}</span>
        </div>
        <!-- top插槽,列表上拉後下方出現的箭頭和loading文字或者動畫 -->
        <!-- 當有topMethod這個props傳入的時候才顯示,此處是備用內容,若是父組件定義了top插槽內容,則備用內容不顯示 -->
      </slot>
      <slot></slot>
      <!-- 默認插槽,就是數據列表 -->
      <slot name="bottom">
        <div class="mint-loadmore-bottom" v-if="bottomMethod">
          <span v-if="bottomStatus === 'loading'" class="mint-loadmore-spinner"></span>
          <span class="mint-loadmore-text">{{ bottomText }}</span>
        </div>
      </slot>
      <!-- bottom插槽,列表上拉後下方出現的箭頭和loading文字或者動畫 -->
      <!-- 當有bottomMethod這個props傳入的時候才顯示,此處是備用內容,若是父組件定義了bottom插槽內容,則備用內容不顯示 -->
    </div>
  </div>
</template>

<style lang="scss" scoped>
  .mint-loadmore {
    overflow: hidden;
  }
  .mint-loadmore-content .is-dropped {
    transition: .2s;
  }
  .mint-loadmore-bottom {
    text-align: center;
    height: 50px;
    line-height: 50px;
    margin-bottom: -50px;
  }
  .mint-loadmore-top {
    text-align: center;
    height: 50px;
    line-height: 50px;
    margin-top: -50px;
  }
</style>

<script type="text/babel">
  export default {
    name: 'loadmore',
    components: {
      
    },
    props: {//props後跟着的對象是驗證器,type是類型,default是默認值
      maxDistance: {
        type: Number,
        default: 0
      },
      autoFill: {
        type: Boolean,
        default: true
      },
      distanceIndex: {
        type: Number,
        default: 2
      },
      topPullText: {
        type: String,
        default: '下拉刷新'
      },
      topDropText: {
        type: String,
        default: '釋放更新'
      },
      topLoadingText: {
        type: String,
        default: '加載中...'
      },
      topDistance: {
        type: Number,
        default: 70
      },
      topMethod: {
        type: Function
      },
      bottomPullText: {
        type: String,
        default: '上拉刷新'
      },
      bottomDropText: {
        type: String,
        default: '釋放更新'
      },
      bottomLoadingText: {
        type: String,
        default: '加載中...'
      },
      bottomDistance: {
        type: Number,
        default: 70
      },
      bottomMethod: {//加載下方數據方法
        type: Function
      },
      bottomAllLoaded: {//布爾值,下方數據已經所有加載
        type: Boolean,
        default: false
      }
    },
    data() {
      return {
        translate: 0, //content在y軸移動距離
        scrollEventTarget: null, //scroll元素
        containerFilled: false, //當前滾動的內容是否填充完整
        topText: '', //上方提示文字
        topDropped: false, //下拉刷新是否已經釋放
        bottomText: '', //下方提示文字
        bottomDropped: false, //底部加載是否已經釋放
        bottomReached: false, //是否已經到達底部
        direction: '', //滑動方向
        startY: 0, //開始滑動的時候觸點的的Y座標
        startScrollTop: 0, //開始滑動的時候,scroll盒子的滾動距離
        currentY: 0, //move過程當中觸點的y軸座標
        topStatus: '', //上方loading層狀態,更新後會傳給父組件
        bottomStatus: '' //下方loading層狀態,更新後會傳給父組件
      };
    },
    computed: {
      transform() {//計算屬性transform,,根據translate值變化,用於經過transform樣式改變content盒子的y軸座標
        return this.translate === 0 ? null : 'translate3d(0, ' + this.translate + 'px, 0)';
      }
    },
    watch: {
      topStatus(val) {
        //偵聽器,若是topStatus發生變化,這個函數就會運行,觸發父級組件的事件,並把topStatus新值做爲參數傳過去
        this.$emit('top-status-change', val);
        switch (val) {
          case 'pull':
            this.topText = this.topPullText;
            break;
          case 'drop':
            this.topText = this.topDropText;
            break;
          case 'loading':
            this.topText = this.topLoadingText;
            break;
        }
        //根據topStatus的新值改變上方的提示文字
      },
      bottomStatus(val) {
      //偵聽器,若是bottomStatus發生變化,這個函數就會運行,觸發父級組件的事件,並把bottomStatus新值做爲參數傳過去
        this.$emit('bottom-status-change', val);
        switch (val) {
          case 'pull':
            this.bottomText = this.bottomPullText;
            break;
          case 'drop':
            this.bottomText = this.bottomDropText;
            break;
          case 'loading':
            this.bottomText = this.bottomLoadingText;
            break;
        }
        //根據bottomStatus的新值改變下方的提示文字
      }
    },
    methods: {
      onTopLoaded() {//父級組件裏每次加載完新數據就會調用這個方法
        this.translate = 0;//重置this.translate
        setTimeout(() => {
          this.topStatus = 'pull';//數據加載完以後topStatus變爲pull狀態
        }, 200);
      },
      onBottomLoaded() {//父級組件裏每次加載完新數據就會調用這個方法
        this.bottomStatus = 'pull'; //數據加載完以後bottomStatus變爲pull狀態
        this.bottomDropped = false; //數據加載完以後bottomDropped變爲false
        this.$nextTick(() => {//數據變化後會更新DOM,DOM更新後會調用$nextTick()裏的方法
          if (this.scrollEventTarget === window) {
            document.body.scrollTop += 50;
          } else {
            this.scrollEventTarget.scrollTop += 50;
          }//數據加載完以後讓對應的scroll盒子向下多滾動50px,也就是說多顯示一條數據讓用戶看到
          this.translate = 0;//重置this.translate
        });
        
        if (!this.bottomAllLoaded && !this.containerFilled) {
          this.fillContainer();
        }
      },
      getScrollEventTarget(element) {//獲取overflow:scroll的父級盒子
        let currentNode = element;
        while (currentNode && currentNode.tagName !== 'HTML' &&
          currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
            //當前傳入節點存在且不是html也不是body且是一個元素節點的時候
          let overflowY = document.defaultView.getComputedStyle(currentNode).overflowY;
          //document.defaultView返回document關聯的window對象
          //getComputedStyle()獲取元素的計算樣式
          //overflowY是當前傳入節點的計算樣式overflow-y
          if (overflowY === 'scroll' || overflowY === 'auto') {
            return currentNode; //若是當前節點的overflow-y值是scroll或者auto,那就返回此節點
          }
          currentNode = currentNode.parentNode;//若是不是,那就獲取當前節點的父節點,而後繼續判斷
        }
        return window;//若是都找不到就返回window對象
      },
      getScrollTop(element) {//獲取元素的內容滾動距離
        if (element === window) {
          return Math.max(window.pageYOffset || 0, document.documentElement.scrollTop);
          //window.pageYOffset就是Window.scrollY,文檔在垂直方向滾動距離
        } else {
          return element.scrollTop;
        }
      },
      bindTouchEvents() {//爲mint-loadmore綁定touch事件操做
        this.$el.addEventListener('touchstart', this.handleTouchStart);
        this.$el.addEventListener('touchmove', this.handleTouchMove);
        this.$el.addEventListener('touchend', this.handleTouchEnd);
      },
      init() {
        this.topStatus = 'pull';//topStatus初始值爲pull
        this.bottomStatus = 'pull';//bottomStatus初始值爲pull
        this.topText = this.topPullText;
        this.scrollEventTarget = this.getScrollEventTarget(this.$el);
        //獲取overflow:scroll的父級盒子
        //傳給getScrollEventTarget方法的參數是this.$el,它是當前Vue實例使用的根DOM元素,也就是mint-loadmore
        //this.scrollEventTarget最後獲取到是父組件的page-loadmore-wrapper盒子,由於它overflow:scroll;
        if (typeof this.bottomMethod === 'function') {//父級組件傳入的加載數據函數若是存在的話
          this.fillContainer();//判斷是否數據填充徹底,初始化this.containerFilled的值
          this.bindTouchEvents();//爲mint-loadmore綁定touch事件操做
        }
        if (typeof this.topMethod === 'function') {//父級組件傳入的加載數據函數若是存在的話
          this.bindTouchEvents();//爲mint-loadmore綁定touch事件操做
        }
      },
      fillContainer() {//判斷是否數據填充徹底
        if (this.autoFill) {
          this.$nextTick(() => {
            if (this.scrollEventTarget === window) {
              this.containerFilled = this.$el.getBoundingClientRect().bottom >=
                document.documentElement.getBoundingClientRect().bottom;
            } else {
              this.containerFilled = this.$el.getBoundingClientRect().bottom >=
                this.scrollEventTarget.getBoundingClientRect().bottom;
                //若是mint-loadmore的bottom值大於等於滾動盒子的bottom值,說明數據填充徹底了,this.containerFilled爲true
            }
            if (!this.containerFilled) {
              this.bottomStatus = 'loading';
              this.bottomMethod();
              //若是數據並無填充徹底,則bottomStatus狀態爲loading,執行父組件的加載數據方法
            }
          });
        }
      },
      checkBottomReached() {//檢查是否已經滑到底部
        if (this.scrollEventTarget === window) {
          /**
           * fix:scrollTop===0
           */
          return document.documentElement.scrollTop || document.body.scrollTop + document.documentElement.clientHeight >= document.body.scrollHeight;
          //若是scroll元素是window的話,就判斷文檔滑動距離加上文檔高度是否大於等於body的內容高度
        } else {
          return parseInt(this.$el.getBoundingClientRect().bottom) <= parseInt(this.scrollEventTarget.getBoundingClientRect().bottom) + 1;
        }
      },
      handleTouchStart(event) {
        this.startY = event.touches[0].clientY;
        //TouchEvent.touches返回全部當前在與觸摸表面接觸的Touch對象
        //Touch對象表示在觸控設備上的觸摸點
        //Touch.clientY,觸點相對於可見視區上邊沿的的Y座標
        //this.startY是開始滑動的時候觸點的Y座標
        this.startScrollTop = this.getScrollTop(this.scrollEventTarget);
        //開始滑動的時候,scroll盒子的滾動距離
        this.bottomReached = false;
        //是否已經滑動到底部
        if (this.topStatus !== 'loading') {//若是上方提示塊並未處於加載階段就重置topStatus和topDropped
          this.topStatus = 'pull';
          this.topDropped = false;
        }
        if (this.bottomStatus !== 'loading') {//若是下方提示塊並未處於加載階段就重置bottomStatus和bottomDropped
          this.bottomStatus = 'pull';
          this.bottomDropped = false;
        }
      },
      handleTouchMove(event) {
        if (this.startY < this.$el.getBoundingClientRect().top && this.startY > this.$el.getBoundingClientRect().bottom) {
          return;
        }
        //若是觸點在mint-loadmore以外就退出move事件
        this.currentY = event.touches[0].clientY;
        //this.currentY是move過程當中觸點的y軸座標
        let distance = (this.currentY - this.startY) / this.distanceIndex;
        //滑動的距離
        this.direction = distance > 0 ? 'down' : 'up';
        //判斷滑動方向
        if (typeof this.topMethod === 'function' && this.direction === 'down' &&
          this.getScrollTop(this.scrollEventTarget) === 0 && this.topStatus !== 'loading') {
          //若是滑到了頂部
          event.preventDefault();//阻止默認事件
          event.stopPropagation();//阻止事件冒泡
          if (this.maxDistance > 0) {
            this.translate = distance <= this.maxDistance ? distance - this.startScrollTop : this.translate;
          } else {
            this.translate = distance - this.startScrollTop;
            //隨着滑動來更新translate值,translate值變化,計算屬性transform就隨之變化,content盒子就在y軸上向下移動
          }
          if (this.translate < 0) {//剛滑到頂部滑不動,會頓一下
            this.translate = 0;
          }
          this.topStatus = this.translate >= this.topDistance ? 'drop' : 'pull';
          //topDistance默認70,拉動距離超過70下方箭頭就變個方向
        }
        if (this.direction === 'up') {//若是是向上滑動,那就是底部加載,就判斷是否已經滑到底部
          this.bottomReached = this.bottomReached || this.checkBottomReached();
        }
        if (typeof this.bottomMethod === 'function' && this.direction === 'up' &&
          this.bottomReached && this.bottomStatus !== 'loading' && !this.bottomAllLoaded) {
          //若是拉到底部了且數據沒有加載完
          event.preventDefault();//阻止默認事件
          event.stopPropagation();//阻止事件冒泡
          if (this.maxDistance > 0) {
            this.translate = Math.abs(distance) <= this.maxDistance
              ? this.getScrollTop(this.scrollEventTarget) - this.startScrollTop + distance : this.translate;
          } else {
            this.translate = this.getScrollTop(this.scrollEventTarget) - this.startScrollTop + distance;
            //隨着滑動來更新translate值,translate值變化,計算屬性transform就隨之變化,content盒子就在y軸上向上移動
          }
          if (this.translate > 0) {//剛滑到底部滑不動,會頓一下
            this.translate = 0;
          }
          this.bottomStatus = -this.translate >= this.bottomDistance ? 'drop' : 'pull';
          //bottomDistance默認70,拉動距離超過70下方箭頭就變個方向
        }
        this.$emit('translate-change', this.translate);//觸發父組件事件,這個是上拉刷新的時候用的
      },
      handleTouchEnd() {
        if (this.direction === 'down' && this.getScrollTop(this.scrollEventTarget) === 0 && this.translate > 0) {
          //下拉刷新
          this.topDropped = true;//drop狀態變動,content添加is-dropped樣式,回到原點動畫
          if (this.topStatus === 'drop') {
            this.translate = '50';
            this.topStatus = 'loading';
            this.topMethod();
            //若是topStatus仍是drop狀態,說明剛放手,那就讓content回到距離頂部50px的地方,而後改變topStatus爲loading,而後執行父組件加載新數據的方法
          } else {
            this.translate = '0';
            this.topStatus = 'pull';
            //若是沒有從超過70的地方釋放,那就回到初始狀態,不加載新數據
          }
        }
        if (this.direction === 'up' && this.bottomReached && this.translate < 0) {
          //底部加載
          this.bottomDropped = true;//drop狀態變動,content添加is-dropped樣式,回到原點動畫
          this.bottomReached = false;//改變是否到達底部狀態
          if (this.bottomStatus === 'drop') {
            this.translate = '-50';
            this.bottomStatus = 'loading';
            this.bottomMethod();
            //若是bottomStatus仍是drop狀態,說明剛放手,那就讓content回到距離底部50px的地方,而後改變bottomStatus爲loading,而後執行父組件加載新數據的方法
          } else {
            this.translate = '0';
            this.bottomStatus = 'pull';
            //若是沒有從超過70的地方釋放,那就回到初始狀態,不加載新數據
          }
        }
        this.$emit('translate-change', this.translate);//觸發父組件事件,這個是上拉刷新的時候用的
        this.direction = '';//清空方向
      }
    },
    mounted() {//mounted的時候調用init()初始化組件狀態
      this.init();
    }
  };
</script>

實現原理就是,外面有個wrapper盒子有死高度且擁有樣式overflow:scroll;的樣式,這樣它的內容超出後就是可滾動的,它的滾動高度scrollTop就能夠拿來計算用。wrapper盒子裏的內容除了數據列表之外還有一個loading層,這個loading層就是已經到頂部再繼續下拉 或者 已經到底部再上拉的時候纔會顯示出來,平時的時候利用maring負值改變y軸方向的位置隱藏起來,它裏面就是loading動畫和一個小箭頭提示標誌,滑動的時候改變裏面content盒子的y軸座標,而後將loading層顯示出來,釋放的時候讓content盒子回到距離原位一個loading層高度的地方,而後發請求加載數據,等數據加載好了再次把全部DOM的狀態迴歸到默認狀態。app

相關文章
相關標籤/搜索