UNPKG

weex-nuke

Version:

基于 Rax 、Weex 的高性能组件体系 ~~

454 lines (398 loc) 16.2 kB
'use strict'; /** @jsx createElement */ Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _rax = require('rax'); var _nukeEnv = require('../Env/index.js'); var _nukeView = require('../View/index.js'); var _nukeView2 = _interopRequireDefault(_nukeView); var _nukeRefreshControl = require('../RefreshControl/index.js'); var _nukeRefreshControl2 = _interopRequireDefault(_nukeRefreshControl); var _styles = require('./styles.js'); var _styles2 = _interopRequireDefault(_styles); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var SCROLLVIEW_CLS = 'rax-scrollview'; var LOADMORE_THRESHOLD = 500; var ON_SCROLL_THROTTLE = 50; var FULL_WIDTH = 750; /** * ScrollView * @description 可滚动容器 */ var ScrollView = function (_Component) { _inherits(ScrollView, _Component); function ScrollView(props) { _classCallCheck(this, ScrollView); var _this = _possibleConstructorReturn(this, (ScrollView.__proto__ || Object.getPrototypeOf(ScrollView)).call(this, props)); _this.state = { loadmoreretry: 0 }; _this.timer = null; _this.childrenAll = {}; _this.scrollStartFlag = false; _this.lastScrollEventTriggerTime = 0; _this.lastScrollContentSize = 0; _this.lastScrollDistance = 0; _this.handleScroll = _this.handleScroll.bind(_this); _this.scrollTo = _this.scrollTo.bind(_this); _this.scrollToElement = _this.scrollToElement.bind(_this); _this.resetLoadmore = _this.resetLoadmore.bind(_this); _this.checkScrolling = _this.checkScrolling.bind(_this); return _this; } _createClass(ScrollView, [{ key: 'checkScrolling', value: function checkScrolling() { var now = new Date().getTime(); if (this.lastScrollEventTriggerTime > 0 && now - this.lastScrollEventTriggerTime > 200) { this.scrollStartFlag = false; clearTimeout(this.timer); this.timer = null; this.props.onScrollEnd && this.props.onScrollEnd(); } } }, { key: 'handleScroll', value: function handleScroll(e) { var _props = this.props, onScroll = _props.onScroll, onScrollStart = _props.onScrollStart, horizontal = _props.horizontal, onEndReached = _props.onEndReached, onEndReachedThreshold = _props.onEndReachedThreshold; if (_nukeEnv.isWeb) { if (!this.scrollStartFlag) { // first time trigger onScroll this.scrollStartFlag = true; onScrollStart && onScrollStart(e); } clearTimeout(this.timer); this.lastScrollEventTriggerTime = new Date().getTime(); if (onScroll) { e.nativeEvent = { contentOffset: { x: e.target.scrollLeft, y: e.target.scrollTop } }; onScroll && onScroll(e); } this.timer = setTimeout(this.checkScrolling, 200); if (onEndReached) { if (!this.scrollerNode) { this.scrollerNode = (0, _rax.findDOMNode)(this.refs.scroller); this.scrollerContentNode = (0, _rax.findDOMNode)(this.refs.contentContainer); this.scrollerNodeSize = horizontal ? this.scrollerNode.offsetWidth : this.scrollerNode.offsetHeight; } // NOTE:in iOS7/8 offsetHeight/Width is is inaccurate ( use scrollHeight/Width ) var scrollContentSize = horizontal ? this.scrollerNode.scrollWidth : this.scrollerNode.scrollHeight; var scrollDistance = horizontal ? this.scrollerNode.scrollLeft : this.scrollerNode.scrollTop; var isEndReached = scrollContentSize - scrollDistance - this.scrollerNodeSize < onEndReachedThreshold; var isScrollToEnd = scrollDistance > this.lastScrollDistance; var isLoadedMoreContent = scrollContentSize !== this.lastScrollContentSize; if (isEndReached && isScrollToEnd && isLoadedMoreContent) { this.lastScrollContentSize = scrollContentSize; onEndReached(e); } this.lastScrollDistance = scrollDistance; } } if (_nukeEnv.isWeex) { e.nativeEvent = { contentOffset: { // HACK: weex scroll event value is opposite of web x: -e.contentOffset.x, y: -e.contentOffset.y } }; onScroll && onScroll(e); } } /** * 兼容旧的 resetScroll */ }, { key: 'resetLoadmore', value: function resetLoadmore() { if (_nukeEnv.isWeb) { this.lastScrollContentSize = 0; this.lastScrollDistance = 0; } else { this.refs.scroller.resetLoadmore(); } } }, { key: 'scrollTo', value: function scrollTo() { var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var horizontal = this.props.horizontal; var _options$x = options.x, x = _options$x === undefined ? 0 : _options$x, _options$y = options.y, y = _options$y === undefined ? 0 : _options$y, _options$animated = options.animated, animated = _options$animated === undefined ? true : _options$animated, _options$offset = options.offset, offset = _options$offset === undefined ? 0 : _options$offset; var offsetResult = parseInt(horizontal ? x : y || offset, 10); if (_nukeEnv.isWeex) { var dom = require('@weex-module/dom'); var contentContainer = (0, _rax.findDOMNode)(this.refs.contentContainer); dom.scrollToElement(contentContainer.ref, { offset: offsetResult, animated: animated }); } else { var pixelRatio = document.documentElement.clientWidth / FULL_WIDTH; if (horizontal) { (0, _rax.findDOMNode)(this.refs.scroller).scrollLeft = pixelRatio * offsetResult; } else { (0, _rax.findDOMNode)(this.refs.scroller).scrollTop = pixelRatio * offsetResult; } } } }, { key: 'scrollToElement', value: function scrollToElement(ref) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var horizontal = this.props.horizontal; var _options$offset2 = options.offset, offset = _options$offset2 === undefined ? 0 : _options$offset2, _options$animated2 = options.animated, animated = _options$animated2 === undefined ? true : _options$animated2; if (_nukeEnv.isWeex) { var dom = require('@weex-module/dom'); // const contentContainer = findDOMNode(this.refs.contentContainer); dom.scrollToElement((0, _rax.findDOMNode)(ref), { offset: offset, animated: animated }); } else { var pixelRatio = document.documentElement.clientWidth / FULL_WIDTH; // if (offset >= 0) { var refDOM = (0, _rax.findDOMNode)(ref); if (refDOM) { if (horizontal) { (0, _rax.findDOMNode)(this.refs.scroller).scrollLeft = refDOM.offsetLeft + pixelRatio * offset; } else { (0, _rax.findDOMNode)(this.refs.scroller).scrollTop = refDOM.offsetTop + pixelRatio * offset; } // } } } } }, { key: 'splitChildren', value: function splitChildren() { var children = this.props.children; if (!children) { this.childrenAll = { contents: null, refreshContent: null }; return; } var contents = []; var refreshContent = void 0; if (!Array.isArray(children)) { children = [children]; } children.forEach(function (child) { if (!child) return; if (child.type && child.type.displayName === _nukeRefreshControl2.default.displayName) { refreshContent = child; } else { contents.push(child); } }); this.childrenAll = { contents: contents, refreshContent: refreshContent }; } }, { key: 'hideWebScrollBar', value: function hideWebScrollBar() { var styleNode = document.createElement('style'); styleNode.id = 'rax-scrollview-style'; document.head.appendChild(styleNode); styleNode.innerHTML = '.' + SCROLLVIEW_CLS + '::-webkit-scrollbar{display: none;}'; } }, { key: 'render', value: function render() { var _props2 = this.props, style = _props2.style, scrollEventThrottle = _props2.scrollEventThrottle, showsHorizontalScrollIndicator = _props2.showsHorizontalScrollIndicator, showsVerticalScrollIndicator = _props2.showsVerticalScrollIndicator, onEndReached = _props2.onEndReached, onScrollStart = _props2.onScrollStart, onScrollEnd = _props2.onScrollEnd, onScroll = _props2.onScroll, children = _props2.children, horizontal = _props2.horizontal, onEndReachedThreshold = _props2.onEndReachedThreshold, others = _objectWithoutProperties(_props2, ['style', 'scrollEventThrottle', 'showsHorizontalScrollIndicator', 'showsVerticalScrollIndicator', 'onEndReached', 'onScrollStart', 'onScrollEnd', 'onScroll', 'children', 'horizontal', 'onEndReachedThreshold']); var showScrollBar = this.props.showScrollBar; if (typeof showScrollBar === 'undefined') { showScrollBar = horizontal ? showsHorizontalScrollIndicator : showsVerticalScrollIndicator; } var contentContainerStyle = [horizontal && _styles2.default.contentContainerHorizontal, this.props.contentContainerStyle]; // bugfix: fix scrollview flex in ios 78 if (!_nukeEnv.isWeex && !horizontal) { contentContainerStyle.push(_styles2.default.containerWebStyle); } this.splitChildren(); var contentChild = this.childrenAll.contents; var refreshContent = this.childrenAll.refreshContent; var contentContainer = (0, _rax.createElement)( _nukeView2.default, { ref: 'contentContainer', style: contentContainerStyle }, contentChild ); var baseStyle = horizontal ? _styles2.default.horizontal : _styles2.default.vertical; var scrollerStyle = Object.assign({}, baseStyle, _nukeEnv.isWeb ? _styles2.default.scrollerWeb : {}, this.props.style); if (_nukeEnv.isWeex) { var nativeProps = _extends({ ref: 'scroller', style: scrollerStyle, showScrollbar: showScrollBar, onLoadmore: onEndReached, onScrollStart: onScrollStart, onScrollEnd: onScrollEnd, onScroll: onScroll ? this.handleScroll : null, loadmoreoffset: parseInt(onEndReachedThreshold, 10), loadmoreretry: true, offsetAccuracy: 20, scrollDirection: horizontal ? 'horizontal' : 'vertical' }, others); return (0, _rax.createElement)( 'scroller', nativeProps, refreshContent, contentContainer ); } /** * trigger times throttle */ var handleScroll = this.handleScroll; if (scrollEventThrottle) { handleScroll = throttle(handleScroll, scrollEventThrottle); } var webProps = _extends({ ref: 'scroller', style: scrollerStyle, onScroll: handleScroll, id: 'scroller_rv' }, others); if (!showScrollBar) { this.hideWebScrollBar(); } return (0, _rax.createElement)( _nukeView2.default, _extends({}, webProps, { className: SCROLLVIEW_CLS }), refreshContent ? (0, _rax.createElement)(_nukeRefreshControl2.default, _extends({}, refreshContent.props, { listId: webProps.id, refreshingTime: 100, refreshing: refreshContent.props.refreshing })) : null, contentContainer ); } }]); return ScrollView; }(_rax.Component); function throttle(func, wait) { var ctx = void 0; var args = void 0; var rtn = void 0; var timeoutID = void 0; var last = 0; function call() { timeoutID = 0; last = +new Date(); rtn = func.apply(ctx, args); ctx = null; args = null; } return function throttled() { ctx = this; args = arguments; var delta = new Date() - last; if (!timeoutID) { if (delta >= wait) call();else timeoutID = setTimeout(call, wait - delta); } return rtn; }; } ScrollView.propTypes = { /** * ScrollView 样式 style of ScrollView */ style: _rax.PropTypes.any, /** * 滚动时回调 callback when scrolling */ onScroll: _rax.PropTypes.func, /** * 滚动开始时回调 callback when scrolling */ onScrollStart: _rax.PropTypes.func, /** * 滚动结束时回调 callback when scrolling */ onScrollEnd: _rax.PropTypes.func, /** * 加载到底部时回调 callback when scrolling to bottom */ onEndReached: _rax.PropTypes.func, /** * 是否横向 is horizontal */ horizontal: _rax.PropTypes.boolean, /** * 触发加载到底部回调的位移 offset of scrolling to bottom event been triggered */ onEndReachedThreshold: _rax.PropTypes.number, /** * 是否展示横向滚动条 is show horizontal scrollbar */ showsHorizontalScrollIndicator: _rax.PropTypes.boolean, /** * 是否展示纵向滚动条 is show vertical scrollbar */ showsVerticalScrollIndicator: _rax.PropTypes.boolean, /** * 内容容器样式 the style of content's wrap container */ contentContainerStyle: _rax.PropTypes.any, /** * onScroll 事件触发节流时间 the time inteval of next onScroll event being triggered */ scrollEventThrottle: _rax.PropTypes.number, children: _rax.PropTypes.any }; ScrollView.defaultProps = { style: {}, contentContainerStyle: {}, horizontal: false, onScroll: null, onScrollStart: null, onScrollEnd: null, onEndReached: function onEndReached() {}, scrollEventThrottle: ON_SCROLL_THROTTLE, onEndReachedThreshold: LOADMORE_THRESHOLD, showsHorizontalScrollIndicator: true, showsVerticalScrollIndicator: true }; exports.default = ScrollView; module.exports = exports['default'];