import omit from 'omit.js'; import React, { createElement } from 'react'; import './ScrollView.scss'; const classNames = require('classnames'); import { ScrollViewProps } from './ScrollView.d'; function easeOutScroll(from, to, callback) { if (from === to || typeof from !== 'number') { return; } let change = to - from; const dur = 500; const sTime = +new Date(); function linear(t, b, c, d) { return (c * t) / d + b; } const isLarger = to >= from; function step() { from = linear(+new Date() - sTime, from, change, dur); if ((isLarger && from >= to) || (!isLarger && to >= from)) { callback(to); return; } callback(from); requestAnimationFrame(step); } step(); } function throttle(fn, delay) { let timer = null; return function () { clearTimeout(timer); timer = setTimeout(function () { fn(); }, delay); }; } class ScrollView extends React.Component { constructor() { super(...arguments); } onTouchMove = (e) => { e.stopPropagation(); }; componentDidMount() { setTimeout(() => { const props = this.props; if (props.scrollY && typeof props.scrollTop === 'number') { if ('scrollWithAnimation' in props) { if (this.container) easeOutScroll(0, props.scrollTop, (pos) => { this.container.scrollTop = pos; }); } else { if (this.container) this.container.scrollTop = props.scrollTop; } this._scrollTop = props.scrollTop; } if (props.scrollX && typeof props.scrollLeft === 'number') { if ('scrollWithAnimation' in props) { easeOutScroll(0, props.scrollLeft, (pos) => { this.container.scrollLeft = pos; }); } else { this.container.scrollLeft = props.scrollLeft; } this._scrollLeft = props.scrollLeft; } }, 10); } componentWillReceiveProps(nextProps) { const props = this.props; // Y 轴滚动 if ( nextProps.scrollY && typeof nextProps.scrollTop === 'number' && nextProps.scrollTop !== this._scrollTop ) { if ('scrollWithAnimation' in nextProps) { easeOutScroll(this._scrollTop, nextProps.scrollTop, (pos) => { this.container.scrollTop = pos; }); } else { this.container.scrollTop = nextProps.scrollTop; } this._scrollTop = nextProps.scrollTop; } // X 轴滚动 if ( nextProps.scrollX && typeof props.scrollLeft === 'number' && nextProps.scrollLeft !== this._scrollLeft ) { if ('scrollWithAnimation' in nextProps) { easeOutScroll(this._scrollLeft, nextProps.scrollLeft, (pos) => { this.container.scrollLeft = pos; }); } else { this.container.scrollLeft = nextProps.scrollLeft; } this._scrollLeft = nextProps.scrollLeft; } } render() { const { className, onScroll, onScrollToUpper, onScrollToLower, scrollX, scrollY } = this.props; let { upperThreshold = 50, lowerThreshold = 50 } = this.props; const cls = classNames( 'taro-scroll', { [`taro-scroll-view__scroll-x`]: scrollX, [`taro-scroll-view__scroll-y`]: scrollY, }, className, ); upperThreshold = parseInt(upperThreshold); lowerThreshold = parseInt(lowerThreshold); const uperAndLower = () => { const { offsetWidth, offsetHeight, scrollLeft, scrollTop, scrollHeight, scrollWidth } = this.container || {}; if ( onScrollToLower && ((this.props.scrollY && offsetHeight + scrollTop + lowerThreshold >= scrollHeight) || (this.props.scrollX && offsetWidth + scrollLeft + lowerThreshold >= scrollWidth)) ) { onScrollToLower(); } if ( onScrollToUpper && ((this.props.scrollY && scrollTop <= upperThreshold) || (this.props.scrollX && scrollLeft <= upperThreshold)) ) { onScrollToUpper(); } }; const uperAndLowerThrottle = throttle(uperAndLower, 16); const _onScroll = (e) => { const { scrollLeft, scrollTop, scrollHeight, scrollWidth } = this.container; this._scrollLeft = scrollLeft; this._scrollTop = scrollTop; e.detail = { scrollLeft, scrollTop, scrollHeight, scrollWidth, }; uperAndLowerThrottle(); onScroll && onScroll(e); }; return (
{ this.container = container; }} {...omit(this.props, [ 'className', 'scrollTop', 'scrollLeft', 'scrollX', 'scrollY', 'onScrollToLower', 'scrollWithAnimation', ])} className={cls} onScroll={_onScroll} onTouchMove={this.onTouchMove} > {this.props.children}
); } } export default ScrollView;