weex-nuke
Version:
基于 Rax 、Weex 的高性能组件体系 ~~
454 lines (398 loc) • 16.2 kB
JavaScript
'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'];