網上有不少針對vue封裝的移動端UI組件庫,但react的移動端UI組件庫貌似只有Google的
material UI和阿里的 ant design mobile。阿里的下拉刷新又不符合項目的風格,只能
本身實現了。
採用better-scroll+react實現。
複製代碼
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; 複製代碼
那位大佬有更好的實現方式 歡迎支出
歡迎加羣
複製代碼