当前位置:网站首页>Write a drop-down refresh component

Write a drop-down refresh component

2022-08-10 03:21:00 small white eyes

       在移动端开发中,不管是iOS还是android ,The drop-down list is loaded frequently,So the necessity of realizing this function is self-evident,There are also many libraries on the side that can do this.In general, pull-down refresh or pull-up loading is achieved by adding sliding event listeners,Judging page swipe distance and gestures to determine when to load data and give feedback,Apart from that foriOS和androidenvironmental differences,Fault tolerance may be required where necessary.

The core logic is to rely ontouchstart touchmove touchend 事件监听做处理,

  1. Touch to start will execute touchstart事件,记录当前的Y坐标 startY;
  2. Listen as you move your fingertouchMove事件:Finger movement records the current portrait in real timeY坐标 pageY , 而pageY - startYis the distance moveddistance;此时最新的topvalue equal to starttopPlus the mouse movement distance: element.style.top = startTop + distance ;
  3. Release your finger to trigger touchend事件,Just if the movement exceeds a certain thresholdthreshold就会执行回调(Event to refresh data)实现回弹效果. 

下面用vue实现:

template:

  <div :class="{ container: refreshEnable }" :style="containerStyle">
    <div v-if="refreshEnable && [1, 2, 3, 4].indexOf(refreshStatus) >= 0">
      <div
        class="top-tip"
        v-if="refreshStatus === 1"
        :class="{ transition: !noAnimation }"
        :style="{ transform: 'translateY(' + (refreshing ? refreshingY : pullY) + 'px)' }"
      >
        <span class="refresh-icon" v-if="pullY > threshold"><img src="@assets/img/up.png" /></span>
        <span class="refresh-icon" v-else><img src="@assets/img/down.png" /></span>
        <span class="refresh-msg">{
   { pullY > threshold ? '松开' : '下拉' }}刷新</span>
      </div>
      <!-- Refresh the prompt information:状态2:开始加载数据,未完成状态 -->
      <div class="top-loading" v-else-if="refreshStatus === 2">
        正在加载…
      </div>
      <!-- Refresh the prompt information:状态3:The data has been loaded and displayed successfully -->
      <div class="top-success" v-else-if="refreshStatus === 3">
       The latest content has been discovered for you
      </div>
      <!-- Refresh the prompt information:状态4:The data has been loaded but nothing new -->
      <div class="top-norefresh" v-else-if="refreshStatus === 4">
There is no new news ahead~
      </div>
    </div>

    <div
      class="wrapper"
      ref="wrapper"
      :class="{ transition: !noAnimation }"
      :style="wrapperY ? { transform: 'translateY(' + wrapperY + 'px)' } : {}"
    >
      
    </div>

    <!-- Always keep space at the bottom to prevent flickering -->
    <div class="common-bottom" v-if="loadMoreEnable && [1, 2].indexOf(loadingStatus) >= 0">
      <!-- Loading tips at the bottom:状态1: 正在加载数据 -->
      <div class="bottom-loading" v-show="loadingStatus === 1">
        正在加载…
      </div>
      <!-- Loading tips at the bottom:状态2: Prompt that the last piece of data has been loaded  -->
      <div class="bottom-limit" v-show="loadingStatus === 2">
        You actually read it all Let's find another location ~ 
      </div>
    </div>
  </div>

script: 

import { throttle } from '@util/throttle';             //节流函数
import scroll from '@mixins/scroll';                   // 监听滚动事件

data:

  data() {
    return {
      refreshStatus: 0,
      loadingStatus: 0,
      pullY: 0,
      refreshing: false,
      loading: false,
      noAnimation: false,
      threshold: 70,
      refreshingY: 0,
    };
  },

props:

  props: {
    // 是否允许下拉刷新
    refreshEnable: {
      type: Boolean,
      default: true,
    },
    // Sliding callback function
    onRefresh: {
      type: Function,
      default() {},
    },
    // The callback function for sliding up
    onLoad: {
      type: Function,
      default() {},
    },
    loadMoreEnable: {
      type: Boolean,
      default: true,
    },
    hasMore: {
      type: Boolean,
      default: true,
    },
    mpsTipHeight: {
      type: Number,
      default: 0,
    },
    navigatorHeight: {
      type: Number,
      default: 0,
    },
    mpsTipShow: {
      type: Boolean,
      default: false,
    },
  },

watch:

  watch: {
    hasMore: {
      handler(newValue) {
        if (this.loadMoreEnable) {
          this.loadingStatus = newValue ? 0 : 2;
        }
      },
      immediate: true,
    },
  },

computed:

  computed: {
    containerStyle() {
      let marginTop = 0;

      if (this.refreshEnable) {
        if (this.mpsTipShow) {
          let baseMarginTop = 29;
          marginTop = `${this.mpsTipHeight + baseMarginTop}px`;
        } else {
          // 随tips高度变化
          marginTop = `${(this.navigatorHeight || 86) - 5}px`;
        }
      }
      return {
        marginTop,
      };
    },
    wrapperY() {
      return this.refreshing ? this.refreshingY : this.pullY;
    },
  },

methods:

  methods: {
    touchEndHandlers(type) {
      if (!this.refreshEnable) {
        return;
      }

      // debugger;
      let vm = this;
      if (vm.refreshing) return;
      vm.refreshStatus = 2;
      vm.refreshing = true;
      let r = vm.onRefresh(type);
      if (r && r.then) {
        r.then((res) => {
          vm.refreshing = false;
          vm.pullY = (54 / 375) * getClientWidth();
          if (res && res === 4) {
            vm.refreshStatus = 4;
          } else if (res && res === 3) {
            vm.refreshStatus = 3;
          }
          setTimeout(() => {
            vm.refreshStatus = 0;
            vm.pullY = 0;
          }, 500);
        }).catch((error) => {
          vm.refreshStatus = 0;
          vm.refreshing = false;
          vm.pullY = 0;
          console.error(error);
        });
      }
    },
    // Swipe-triggered function:throttling
    scrollHandlers() {
      let vm = this;
      let scrollTop = this.getScrollTop();
      let bodyHeight = document.documentElement.clientHeight || document.body.clientHeight;
      let bodyWidth = document.documentElement.clientWidth || document.body.clientWidth;
      if (!vm.$refs.wrapper || !this.hasMore || !this.loadMoreEnable) return;
      let containerHeight = vm.$refs.wrapper.clientHeight;
      // If in the end load data,距离底端150pxstart loading data
      if ((scrollTop + bodyHeight) > (containerHeight - ((200 / 375) * bodyWidth)) && !vm.loading) {
        vm.loading = true;
        // onLoad为一个返回promise对象的函数
        vm.loadingStatus = 1;
        let l = vm.onLoad();
        if (l && l.then) {
          l.then((res) => {
            if (res === -2) {
              vm.loadingStatus = 2;
              vm.loading = false;
            } else if (res === -1) {
              vm.loadingStatus = 0;
              vm.loading = false;
            } else {
              vm.loading = false;
            }
          }).catch((error) => {
            vm.loadingStatus = 0;
            vm.loading = false;
            console.error(error);
          });
        }
      }
    },
    forceRefresh(type) {
      window.scrollTo(0, 0);
      this.touchEndHandlers(type);
    },
  },

mounted:

  mounted() {
    let vm = this;

    // Add sliding event listener,Used to determine when to load data,节流
    window.addEventListener('scroll', throttle(vm.scrollHandlers, 20), true);

    if (this.refreshEnable) {
      // 添加滑动事件,Used for judgment monitoringscrollTop:1. 判断滑动方向 2.Determine if it's nearing the end
      let start = false;
      let startY = null;
      let lastMove = null;
      // After pulling down and rebounding,刷新过程中,即refreshing为true时,The list bounces back to a certain height
      vm.refreshingY = (54 / 375) * getClientWidth();

      // If only a small area is swiped,Then the place where the touch starts needs to be within this area
      this.$refs.wrapper.addEventListener('touchstart', () => {
        start = true;
      });
      window.addEventListener('touchstart', (ev) => {
        lastMove = ev.changedTouches[0].pageY;
      });
      window.addEventListener(
        'touchmove',
        (ev) => {
          let scrollTop = this.getScrollTop();
          if (start && scrollTop <= 0 && lastMove !== null) {
            let now = ev.changedTouches[0].pageY;
            let diff = now - lastMove;
            // Determine the direction of the instantaneous slide
            if (diff > 0) {
              if (ev.cancelable) {
                ev.preventDefault();
              }

              // Block the browser by defaultscroll事件
              vm.refreshStatus = 1;
            } else {
              // Swipe up momentarily,Scroll down overall
              if (vm.pullY > 0) {
                if (ev.cancelable) {
                  ev.preventDefault();
                }
                vm.refreshStatus = 1;
              }
            }
            if (!startY) {
              startY = now;
            }
            let targetHeight = now - startY;
            vm.pullY = Math.max(targetHeight, 0) / 2;
          }
          // Stop the animation when the finger is pulled down
          vm.noAnimation = true;
          lastMove = ev.changedTouches[0].pageY;
        },
        { passive: false }
      );
      window.addEventListener('touchend', () => {
        if (vm.refreshing) return;
        if (vm.pullY > vm.threshold) {
          vm.touchEndHandlers();
        } else {
          vm.pullY = 0;
          vm.refreshStatus = 0;
        }
        startY = null;
        lastMove = null;
        start = false;
        vm.noAnimation = false;
      });
    }
  },

mixins:

/**
 * 页面滚动
 * @module mixins/scroll
 */
export default {
  methods: {
    /**
     * 是否为iOS
     * @ignore
     * @return {Boolean} 是否为iOS
     */
    isIOS() {
      const UA = window.navigator.userAgent.toLowerCase();
      return UA && /iphone|ipad|ipod|ios/.test(UA);
    },

    /**
     * Add a continuous scroll event listener for the page
     * 由于ios的UIWebviewThere is scrollingbug,So do compatibility processing
     * @ignore
     * @param {Function} callback The callback function when scrolling occurs
     */
    addBodyScrollListener(callback) {
      document.addEventListener('scroll', callback);
      if (this.isIOS && typeof window.ontouchmove !== 'undefined') {
        document.addEventListener('touchmove', callback);
      }
    },

    /**
     * 获取页面的scrollTop
     * @ignore
     * @return {Number} 页面的scrollTop
     */
    getScrollTop() {
      return document.body.scrollTop || document.documentElement.scrollTop;
    },

    /**
     * Scrolls the page to the specified position
     * @ignore
     * @param {Number} top The abscissa of the target position
     */
    scrollTop(top) {
      document.body.scrollTop = top;
      document.documentElement.scrollTop = top;
    },

    /**
     * rem转换为px
     * @ignore
     * @param {String} rem 待转换的rem值
     * @return {Number} 转换后的px值
     */
    rem2px(rem) {
      const { fontSize } = document.getElementsByTagName('html')[0].style;
      return `${parseFloat(rem) * parseFloat(fontSize)}px`;
    },

    /**
     * px转换为rem
     * @ignore
     * @param {String} px 待转换的px值
     * @return {Number} 转换后的rem值
     */
    px2rem(px) {
      const { fontSize } = document.getElementsByTagName('html')[0].style;
      return `${parseFloat(px) / parseFloat(fontSize)}rem`;
    },

    /**
     * 无论是rem还是px单位,are converted to integerspx数值
     * @ignore
     * @param {String} value 输入值
     * @return {Number} 转换后的px数值
     */
    remOrPxToPxNumber(value) {
      if (value.toString().indexOf('rem') > -1) {
        // rem转px
        return parseFloat(this.rem2px(value));
      }
      return parseFloat(value);
    },
  },
};

 

 

原网站

版权声明
本文为[small white eyes]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/222/202208100154475077.html