ridingwind-scrolllist
Version:
pull-up load more drop-down refresh on mobile, AND scroll load more on PC, React Compopnent
304 lines (272 loc) • 9.39 kB
JavaScript
/* eslint-disable max-len,consistent-return */
import React from 'react';
import RSTATES from './RSTATES';
import styles from './ridingWindScrollList.css';
/**
* RidingWindScrollList
* @author RidingWind
* @param {String} id (isRequired): id
* @param {String} currentState (isRequired): 当前状态
* @param {func} executeFunc (isRequired): 执行的方法
* @param {Boolean} hasMore (isRequired): 是否还有更多内容可以加载
* @param {Number} pullDownSpace (isRequired): 下拉距离是否满足要求
* @param {Number} actionSpaceBottom (isRequired): 距离底部多少距离触发加载更多
* @param {Jsx} HeadDOM: 自定义顶部刷新组件
* @param {Jsx} FooterDOM: 自定义底部刷新组件
*/
function addEventFunc(obj, type, fn) {
const RWDOM = obj;
if (RWDOM.attachEvent) {
// 兼容ie
RWDOM[`e${type}${fn}`] = fn;
RWDOM[type + fn] = function () { RWDOM[`e${type}${fn}`](window.event); };
RWDOM.attachEvent(`on${type}`, RWDOM[type + fn]);
} else { RWDOM.addEventListener(type, fn, false, { passive: false }); }
}
function removeEventFunc(obj, type, fn) {
const RWDOM = obj;
if (RWDOM.detachEvent) {
// 兼容ie
RWDOM.detachEvent(`on${type}`, RWDOM[type + fn]);
RWDOM[type + fn] = null;
} else { RWDOM.removeEventListener(type, fn, false); }
}
export default class RidingWindScrollList extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
pageWidth: document.body.clientWidth,
pullDistance: 0,
beforeHeight: 0,
scollLoading: true,
};
}
componentDidMount() {
const { pullDownSpace, actionSpaceBottom, id } = this.props;
this[`${id}_defaultConfig`] = {
container: this[`${id}_container`],
offsetScrollTop: 1,
pullDownSpace,
actionSpaceBottom,
};
if (this.state.pageWidth <= 780) {
// 移动端
addEventFunc(this[`${id}_container`], 'touchstart', this.onTouchStart);
addEventFunc(this[`${id}_container`], 'touchmove', this.onTouchMove);
addEventFunc(this[`${id}_container`], 'touchend', this.onTouchEnd);
} else {
// PC
addEventFunc(this[`${id}_container`], 'scroll', this.scrollFunc);
}
}
componentWillUnmount() {
const { id } = this.props;
if (this.state.pageWidth <= 780) {
// 移动端
removeEventFunc(this[`${id}_container`], 'touchstart', this.onTouchStart);
removeEventFunc(this[`${id}_container`], 'touchmove', this.onTouchMove);
removeEventFunc(this[`${id}_container`], 'touchend', this.onTouchEnd);
} else {
// PC
removeEventFunc(this[`${id}_container`], 'scroll', this.scrollFunc);
}
}
componentWillReceiveProps(nextProps) {
const { executeFunc } = this.props;
if (nextProps.currentState === RSTATES.refreshed) {
setTimeout(() => {
executeFunc(RSTATES.reset);
}, 1000);
}
}
getScrollTopFunc = () => {
const { id } = this.props;
if (this[`${id}_defaultConfig`].container) {
return this[`${id}_defaultConfig`].container.scrollTop;
}
return 0;
}
setScrollTop = (value) => {
const { id } = this.props;
let newVal = value;
if (this[`${id}_defaultConfig`].container) {
const scrollH = this[`${id}_defaultConfig`].container.scrollHeight;
if (value < 0) { newVal = 0; }
if (value > scrollH) { newVal = scrollH; }
this[`${id}_defaultConfig`].container.scrollTop = newVal;
return this[`${id}_defaultConfig`].container.scrollTop;
}
return 0;
}
// 拖拽的缓动公式
easing = (distance) => {
const b = 0;
const d = window.screen.availHeight; // 允许拖拽的最大距离
const c = d / 2.5; // 提示标签最大有效拖拽距离
return (c * Math.sin((distance / d) * (Math.PI / 2))) + b;
}
canRefresh = () => {
const { currentState } = this.props;
return [RSTATES.refreshing, RSTATES.loading].indexOf(currentState) < 0;
}
onPullDownMove = (data) => {
const { executeFunc, id } = this.props;
if (!this.canRefresh()) return false;
let loaderState;
let diff = data[0].touchMoveY - data[0].touchStartY;
if (diff < 0) {
diff = 0;
}
diff = this.easing(diff);
if (diff > this[`${id}_defaultConfig`].pullDownSpace) {
loaderState = RSTATES.enough;
} else {
loaderState = RSTATES.pulling;
}
this.setState({
pullDistance: diff,
});
executeFunc(loaderState);
}
onPullDownRefresh = () => {
const { executeFunc, currentState } = this.props;
if (!this.canRefresh()) return false;
if (currentState === RSTATES.pulling) {
this.setState({ pullDistance: 0 });
executeFunc(RSTATES.reset);
} else {
this.setState({
pullDistance: 0,
});
executeFunc(RSTATES.refreshing);
}
}
onPullUpMove = () => {
const { executeFunc } = this.props;
if (!this.canRefresh()) return false;
this.setState({
pullDistance: 0,
});
executeFunc(RSTATES.loading);
}
scrollFunc = () => {
// scrollHeight: 正文全文高; offsetHeight: 可见区域高; scrollTop: 被卷去的高;
const { beforeHeight, scollLoading } = this.state;
const { actionSpaceBottom } = this.props;
const { scrollTop, offsetHeight } = this[`${this.props.id}_container`];
const { scrollHeight } = this[`${this.props.id}_container_box`];
if (beforeHeight === scrollHeight) {
if (
((scrollHeight - offsetHeight - scrollTop) <= actionSpaceBottom) && scollLoading
) {
this.setState({
scollLoading: false,
});
this.onPullUpMove();
}
} else {
this.setState({
beforeHeight: scrollHeight,
scollLoading: true,
});
}
}
onTouchStart = (event) => {
const targetEvent = event.changedTouches[0];
this.startX = targetEvent.clientX;
this.startY = targetEvent.clientY;
}
onTouchMove = (event) => {
const { id } = this.props;
const scrollTop = this.getScrollTopFunc();
const scrollH = this[`${id}_defaultConfig`].container.scrollHeight;
const conH = this[`${id}_defaultConfig`].container.offsetHeight;
const targetEvent = event.changedTouches[0];
const curX = targetEvent.clientX;
const curY = targetEvent.clientY;
const diffX = curX - this.startX;
const diffY = curY - this.startY;
// 判断垂直移动距离是否大于5 && 横向移动距离小于纵向移动距离
if (Math.abs(diffY) > 5 && Math.abs(diffY) > Math.abs(diffX)) {
// 滚动距离小于设定值 &&回调onPullDownMove 函数,并且回传位置值
if (diffY > 5 && scrollTop < this[`${id}_defaultConfig`].offsetScrollTop) {
event.preventDefault();
this.onPullDownMove([{
touchStartY: this.startY,
touchMoveY: curY,
}]);
} else if (diffY < 0 && (scrollH - scrollTop - conH) < this[`${id}_defaultConfig`].actionSpaceBottom) {
// 滚动距离距离底部小于设定值
// event.preventDefault();
// this.onPullUpMove([{
// touchStartY: this.startY,
// touchMoveY: curY,
// }]);
this.onPullUpMove();
}
}
}
onTouchEnd = (event) => {
const { id } = this.props;
const scrollTop = this.getScrollTopFunc();
const targetEvent = event.changedTouches[0];
const curX = targetEvent.clientX;
const curY = targetEvent.clientY;
const diffX = curX - this.startX;
const diffY = curY - this.startY;
// 判断垂直移动距离是否大于5 && 横向移动距离小于纵向移动距离
if (Math.abs(diffY) > 5 && Math.abs(diffY) > Math.abs(diffX)) {
if (diffY > 5 && scrollTop < this[`${id}_defaultConfig`].offsetScrollTop) {
// 回调onPullDownRefresh 函数,即满足刷新条件
this.onPullDownRefresh();
}
}
}
render() {
const {
id, children, currentState, HeadDOM, hasMore, FooterTipDOM
// FooterDOM,
} = this.props;
const { pullDistance } = this.state;
const msgStyle = pullDistance ? {
WebkitTransform: `translate3d(0, ${pullDistance}px, 0)`,
transform: `translate3d(0, ${pullDistance}px, 0)`,
} : null;
const newClassName = currentState ? styles[`state_${currentState}`] : styles.ridingwind;
const footerClassName = hasMore ? styles.pullLoadFooterDefault : styles.pullLoadFooterDefaultNomore;
return (
<div
className={newClassName}
id={id}
ref={(node) => { this[`${id}_container`] = node; }}
>
<div className={styles.pullLoadBody} style={msgStyle}>
<div className={styles.pullLoadHead}>
{
HeadDOM || (
<div className={styles.pullLoadHeadDefault}>
<i />
</div>
)
}
</div>
<div ref={(node) => { this[`${id}_container_box`] = node; }}>
{ children }
</div>
<div className={styles.pullLoadFooter}>
{/* {
FooterDOM || ( */}
<div className={footerClassName}>
{
currentState === RSTATES.loading ? <i /> : ''
}
</div>
{ FooterTipDOM || null}
{/* )
} */}
</div>
</div>
</div>
);
}
}