react 移動端下拉刷新

前提

網上有不少針對vue封裝的移動端UI組件庫,但react的移動端UI組件庫貌似只有Google的
material UI和阿里的 ant design mobile。阿里的下拉刷新又不符合項目的風格,只能
本身實現了。
採用better-scroll+react實現。
複製代碼

效果

爲何要採用better-scroll

better-scroll 是一款重點解決移動端(已支持 PC)各類滾動場景需求的插件。它的核心
是借鑑的 iscroll 的實現,它的 API 設計基本兼容 iscroll,在 iscroll 的基礎上又
擴展了一些 feature 以及作了一些性能優化。 
另外 better-scroll 中已經提供了下拉刷新 上拉加載更多的方法,我要作的也是在其方法
內完善我要的效果
複製代碼

下拉刷新

pullDownRefresh選項,用來配置下拉刷新功能。當設置爲 true 或者是一個 Object 的時候,開啓下拉刷新,能夠配置頂部下拉的距離(threshold)來決定刷新時機,以及回彈停留的距離(stop)html

options.pullDownRefresh = {
  threshold: 50, // 當下拉到超過頂部 50px 時,觸發 pullingDown 事件
  stop: 20 // 刷新數據的過程當中,回彈停留在距離頂部還有 20px 的位置
}

this.scroll = new BScroll(this.$refs.wrapper, options)
複製代碼

監聽 pullingDown 事件,刷新數據。並在刷新數據完成以後,調用 finishPullDown() 方法,回彈到頂部邊界vue

this.scroll.on('pullingDown', () => {
  // 刷新數據的過程當中,回彈停留在距離頂部還有20px的位置
  RefreshData()
    .then((newData) => {
      this.data = newData
      // 在刷新數據完成以後,調用 finishPullDown 方法,回彈到頂部
      this.scroll.finishPullDown()
  })
})
複製代碼

上拉加載更多

pullUpLoad選項,用來配置上拉加載功能。當設置爲 true 或者是一個 Object 的時候,能夠開啓上拉加載,能夠配置離底部距離閾值(threshold)來決定開始加載的時機react

options.pullUpLoad = {
  threshold: -20 // 在上拉到超過底部 20px 時,觸發 pullingUp 事件
}

this.scroll = new BScroll(this.$refs.wrapper, options)
複製代碼

監聽 pullingUp 事件,加載新數據。git

this.scroll.on('pullingUp', () => {
  loadData()
    .then((newData) => {
      this.data.push(newData)
  })
})
複製代碼

直接上代碼

import React, { Component } from "react";
import PropTypes from "prop-types";
import BScroll from "better-scroll";
import icon_arrow from "@assets/images/other/arr.png";
//樣式
import "./betterScroll.less";

let defaultPullDownRefresh = {
  threshold: 100,
  stop: 50,
  stopTime: 600,
  txt: {
    success: "刷新成功"
  }
};

let defaultPullUpLoad = {
  threshold: 0,
  txt: {
    more: "加載更多",
    nomore: "我是有底線的"
  }
};

class Scroll extends Component {
  static defaultProps = {
    probeType: 3,
    click: false, // https://ustbhuangyi.github.io/better-scroll/doc/options.html#tap
    startY: 0,
    scrollY: true,
    scrollX: false,
    freeScroll: true,
    scrollbar: true,
    pullDownRefresh: false,
    pullUpLoad: false,
    bounce: true,
    preventDefaultException: {
      className: /(^|\s)originEvent(\s|$)/,
      tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT|TABLE)$/
    },
    eventPassthrough: "",
    isPullUpTipHide: true,
    disabled: false,
    stopPropagation: true
  };

  static propTypes = {
    children: PropTypes.any,
    probeType: PropTypes.number,
    startY: PropTypes.number,
    click: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
    scrollY: PropTypes.bool,
    scrollX: PropTypes.bool,
    freeScroll: PropTypes.bool,
    scrollbar: PropTypes.bool,
    pullDownRefresh: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
    pullUpLoad: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
    pullUpLoadMoreData: PropTypes.func,
    canRenderPullUpTip: PropTypes.bool,
    doPullDownFresh: PropTypes.func,
    doScroll: PropTypes.func,
    doScrollStart: PropTypes.func,
    doScrollEnd: PropTypes.func,

    preventDefaultException: PropTypes.object,
    eventPassthrough: PropTypes.string,
    isPullUpTipHide: PropTypes.bool,
    bounce: PropTypes.bool,
    disabled: PropTypes.bool,
    stopPropagation: PropTypes.bool
  };

  constructor(props, context) {
    super(props, context);

    this.scroll = null; // scroll 實例

    this.isRebounding = false;
    this.pulling = false;

    this.pullDownInitTop = -50;
    // this.pullDownInitTop = 0;

    this.state = {
      isPullUpLoad: false,
      beforePullDown: true,
      pulling: false,
      pullDownStyle: {
        top: `${this.pullDownInitTop}px`
      },
      bubbleY: 0
    };
  }

  createScrollId() {
    return Math.random()
      .toString(36)
      .substr(3, 10);
  }

  componentDidMount() {
    this.initScroll();
  }

  componentDidUpdate(prevProps) {
    if (this.props.children !== prevProps.children) {
      if (!this.state.pulling) {
        this.scroll.refresh();
      }
      if (prevProps.disabled !== this.props.disabled) {
        this.props.disabled ? this.scroll.disable() : this.scroll.enable();
      }
    }
  }

  componentWillUnmount() {
    this.scroll.stop();
    this.scroll.destroy();
    this.scroll = null;
    clearTimeout(this.TimerA);
    clearTimeout(this.TimerB);
  }

  initScroll() {
    let {
      probeType,
      click,
      startY,
      scrollY,
      scrollX,
      freeScroll,
      scrollbar,
      pullDownRefresh,
      pullUpLoad,
      preventDefaultException,
      eventPassthrough,
      bounce,
      stopPropagation
    } = this.props;
    let _pullDownRefresh =
      typeof pullDownRefresh === "object"
        ? {
          ...defaultPullDownRefresh,
          ...pullDownRefresh
        }
        : pullDownRefresh
        ? defaultPullDownRefresh
        : false;

    let _pullUpLoad =
      typeof pullUpLoad === "object"
        ? {
          ...defaultPullUpLoad,
          ...pullUpLoad
        }
        : pullUpLoad
        ? defaultPullUpLoad
        : false;

    this.options = {
      probeType,
      click,
      startY,
      scrollY,
      freeScroll,
      scrollX,
      scrollbar,
      pullDownRefresh: _pullDownRefresh,
      pullUpLoad: _pullUpLoad,
      preventDefaultException,
      eventPassthrough,
      bounce: bounce,
      stopPropagation: stopPropagation
    };
    let wrapper = this.refs.$dom;
    this.scroll = new BScroll(wrapper, this.options);
    this.initEvents();
  }

  initEvents() {
    if (this.options.pullUpLoad) {
      this._initPullUpLoad();
    }
    if (this.options.pullDownRefresh) {
      this._initPullDownRefresh();
    }
    if (this.props.doScrollStart) {
      this.scroll.on("scrollStart", pos => {
        this.props.doScrollStart(pos);
      });
    }
    if (this.props.doScroll) {
      this.scroll.on("scroll", pos => {
        this.props.doScroll(pos);
      });
    }
    if (this.props.doScrollEnd) {
      this.scroll.on("scrollEnd", pos => {
        this.props.doScrollEnd(pos);
      });
    }
    if (this.props.disabled) {
      this.scroll.disable();
    }
  }

  getScrollObj = () => {
    return this.scroll;
  };

  _initPullDownRefresh() {

    this.scroll.on("pullingDown", () => {
      //鬆開手時
      this.setState({
        beforePullDown: false,
        pulling: true
      });
      this.props.doPullDownFresh().then(() => {
        //刷新方法調用成功
        if (!this.scroll) {
          return;
        }
        this.setState({
          pulling: false
        });
        this._reboundPullDown().then(() => {
          this._afterPullDown();
        });
      });
    });

    this.scroll.on("scroll", pos => {
      const { beforePullDown } = this.state;
      if (pos.y < 0) {
        return;
      }

      if (beforePullDown) {
        this.setState({
          bubbleY: Math.max(0, pos.y + this.pullDownInitTop),
          pullDownStyle: {
            top: `${Math.min(pos.y + this.pullDownInitTop, 10)}px`
          }
        });
      } else {
        this.setState({
          bubbleY: 0
        });
      }

      if (this.isRebounding) {
        this.setState({
          pullDownStyle: {
            top: `${10 - (defaultPullDownRefresh.stop - pos.y)}px`
          }
        });
      }
    });
  }

  _reboundPullDown = () => {
    let { stopTime = 4000 } = this.options.pullDownRefresh;
    return new Promise(resolve => {
      this.TimerA = setTimeout(() => {
        this.isRebounding = true;
        this.scroll.finishPullDown();
        resolve();
      }, stopTime);
    });
  };

  _afterPullDown() {
    this.TimerB = setTimeout(() => {
      this.setState({
        beforePullDown: true,
        pullDownStyle: {
          top: `${this.pullDownInitTop}px`
        }
      });
      this.isRebounding = false;
      this.scroll.refresh();
    }, this.scroll.options.bounceTime);
  }

  _initPullUpLoad = () => {
    this.scroll.on("pullingUp", () => {
      this.setState({
        isPullUpLoad: true
      });

      this.props.pullUpLoadMoreData().then(() => {
        if (!this.scroll) {
          return;
        }
        this.setState({
          isPullUpLoad: false
        });
        this.scroll.finishPullUp();
        this.scroll.refresh();
      });
    });
  };

  renderPullUpLoad() {
    let { pullUpLoad, isPullUpTipHide } = this.props;

    if (pullUpLoad && isPullUpTipHide) {
      return (
        <div className="b-pullup-wrapper"> <div className="after-trigger" style={{ lineHeight: ".32rem" }}> <span style={{ color: "#999999", fontSize: ".28rem" }}>{""}</span> </div> </div>
      );
    }

    if (pullUpLoad && this.state.isPullUpLoad) {
      return (
        <div className="b-pullup-wrapper"> <div className="after-trigger" style={{ lineHeight: ".32rem" }}> <i className="loading-icon"></i> <span style={{ color: "#999999", fontSize: "13px" }}> {typeof pullUpLoad === "object" ? pullUpLoad.txt.more : "加載中..."} </span> </div> </div>
      );
    }
    if (pullUpLoad && !this.state.isPullUpLoad) {
      return (
        <div className="b-pullup-wrapper"> <div className="before-trigger"> <span style={{ color: "#999999", fontSize: "13px" }}> {typeof pullUpLoad === "object" ? pullUpLoad.txt.nomore : "加載完成"} </span> </div> </div>
      );
    }
  }

  renderPullUpDown() {
    let { pullDownRefresh } = this.props;
    let { beforePullDown, pulling, pullDownStyle, bubbleY } = this.state;
    let cls = "arrow";
    if (pullDownRefresh && beforePullDown) {
      if (bubbleY > 50) {
        cls += " up";
      }
      return (
        <div className="b-pulldown-wrapper" style={pullDownStyle}> <div className={"after-trigger"}> <img src={icon_arrow} className={cls}/> <span> {bubbleY > 50 ? "鬆開當即刷新" : "下拉刷新"} </span> </div> </div> ); } if (pullDownRefresh && !beforePullDown && pulling) { return ( <div className="b-pulldown-wrapper" style={pullDownStyle}> <div className={"after-trigger"}> <span> 加載中... </span> </div> </div> ); } if (pullDownRefresh && !beforePullDown && !pulling) { return ( <div className="b-pulldown-wrapper" style={pullDownStyle}> <div className={"after-trigger"}> <div> <span> {typeof this.options.pullDownRefresh === "object" ? this.options.pullDownRefresh.txt.success : "刷新完成"} </span> </div> </div> </div> ); } } render() { return ( <div className="b-wrapper" ref="$dom"> <div className="b-scroll-content"> {this.props.children} {this.renderPullUpLoad()} </div> {this.renderPullUpDown()} </div> ); } } export default Scroll; 複製代碼

總結:

那位大佬有更好的實現方式 歡迎支出
歡迎加羣
複製代碼

相關文章
相關標籤/搜索