前提
代码语言:javascript复制网上有很多针对vue封装的移动端UI组件库,但react的移动端UI组件库貌似只有Google的
material UI和阿里的 ant design mobile。阿里的下拉刷新又不符合项目的风格,只能
自己实现了。
采用better-scroll react实现。
效果
为什么要采用better-scroll
代码语言:javascript复制better-scroll 是一款重点解决移动端(已支持 PC)各种滚动场景需求的插件。它的核心
是借鉴的 iscroll 的实现,它的 API 设计基本兼容 iscroll,在 iscroll 的基础上又
扩展了一些 feature 以及做了一些性能优化。
另外 better-scroll 中已经提供了下拉刷新 上拉加载更多的方法,我要做的也是在其方法
内完善我要的效果
下拉刷新
pullDownRefresh选项,用来配置下拉刷新功能。当设置为 true 或者是一个 Object 的时候,开启下拉刷新,可以配置顶部下拉的距离(threshold)来决定刷新时机,以及回弹停留的距离(stop)
代码语言:javascript复制options.pullDownRefresh = {
threshold: 50, // 当下拉到超过顶部 50px 时,触发 pullingDown 事件
stop: 20 // 刷新数据的过程中,回弹停留在距离顶部还有 20px 的位置
}
this.scroll = new BScroll(this.$refs.wrapper, options)
监听 pullingDown 事件,刷新数据。并在刷新数据完成之后,调用 finishPullDown() 方法,回弹到顶部边界
代码语言:javascript复制this.scroll.on('pullingDown', () => {
// 刷新数据的过程中,回弹停留在距离顶部还有20px的位置
RefreshData()
.then((newData) => {
this.data = newData
// 在刷新数据完成之后,调用 finishPullDown 方法,回弹到顶部
this.scroll.finishPullDown()
})
})
复制代码
上拉加载更多
pullUpLoad选项,用来配置上拉加载功能。当设置为 true 或者是一个 Object 的时候,可以开启上拉加载,可以配置离底部距离阈值(threshold)来决定开始加载的时机
代码语言:javascript复制options.pullUpLoad = {
threshold: -20 // 在上拉到超过底部 20px 时,触发 pullingUp 事件
}
this.scroll = new BScroll(this.$refs.wrapper, options)
监听 pullingUp 事件,加载新数据。
代码语言:javascript复制this.scroll.on('pullingUp', () => {
loadData()
.then((newData) => {
this.data.push(newData)
})
})
复制代码
直接上代码
代码语言:javascript复制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;
复制代码
总结:
代码语言:javascript复制那位大佬有更好的实现方式 欢迎支出