weex-nuke
Version:
基于 Rax 、Weex 的高性能组件体系 ~~
425 lines (359 loc) • 11.3 kB
JavaScript
/* @jsx createElement */
'use strict';
import { createElement, cloneElement, render, Component, findDOMNode, PropTypes } from 'rax';
import View from 'nuke-view';
import SwipeEvent from 'nuke-swipe';
import styles from './defaultStyle';
const propTypes = {
onChange: PropTypes.func,
paginationStyle: PropTypes.object
};
/**
* @Slider Entrance
* rx-slider h5 version
**/
export default class Slide extends Component {
constructor(props) {
super(props);
}
state = {
isSwiping: false,
offsetX: '',
loopIdx: 0
}
componentWillReceiveProps(nextProps) {
let initState = this.state;
initState.total = nextProps.children ? nextProps.children.length || 1 : 0;
initState.index = initState.total > 1 ? Math.min(nextProps.index, initState.total - 1) : 0;
initState.height = nextProps.height;
initState.width = parseFloat(nextProps.width) * document.documentElement.clientWidth / 750;
// 新增默认偏移量
initState.offsetX = initState.index * initState.width;
// 新增默认参数
initState.loopIdx = initState.index;
if (initState.total > 1) {
let setup = nextProps.loop ? 1 : initState.index;
}
this.setState(initState);
}
componentWillMount() {
let initState = this.state;
initState.total = this.props.children ? this.props.children.length || 1 : 0;
initState.index = initState.total > 1 ? Math.min(this.props.index, initState.total - 1) : 0;
initState.height = this.props.height;
initState.width = parseFloat(this.props.width) * document.documentElement.clientWidth / 750;
// 新增默认偏移量
initState.offsetX = initState.index * initState.width;
// 新增默认参数
initState.loopIdx = initState.index;
if (initState.total > 1) {
let setup = this.props.loop ? 1 : initState.index;
}
this.setState(initState);
}
componentDidMount() {
if (this.state.total < 2) {
return;
} else {
let x = this.state.index * this.state.width;
this.changeOffset(x, this.state.index);
}
this.autoPlay();
}
autoPlay() {
let that = this;
// 非自动播放的情况 return 掉
if (!Array.isArray(this.props.children) || !this.props.autoPlay || this.state.isSwiping) {
return;
}
clearTimeout(this.autoPlayTimer);
this.autoPlayTimer = setTimeout(() => {
// let toIndex = that.state.total > that.state.index + 1 ? that.state.index + 1 : 0;
that.slideTo();
}, parseFloat(this.props.autoplayInterval));
}
slideTo(index) {
let that = this;
let state = this.state;
if (state.isSwiping || state.total < 2) {
return;
}
// 非传入指定 index 的情况
let noIndex = typeof index === 'undefined';
let diff = noIndex ? state.index : index;
let x = diff * state.width;
//根据index和偏移改变位置
this.changeOffset(x, diff);
if (noIndex) {
// 自加index
this.updateIndex();
} else {
this.setState({ index: diff });
}
//开启下一轮播放
this.setAutoTimer();
}
setAutoTimer() {
let that = this;
clearTimeout(this.autoTimer);
this.autoTimer = setTimeout(() => {
that.autoPlay();
}, 0);
}
// 改变 slider 的框子位置
changeOffset(x, idx, distance) {
// console.log('change', x, idx)
let state = this.state,
width = state.width;
if (!this.refs.swipeView) {
return;
}
// 外框translate3d for translate3d 为了性能
let swipeView = findDOMNode(this.refs.swipeView);
swipeView.style.transform = 'translate3d(' + (- (x)) + 'px, 0px, 0px)';
swipeView.style.webkitTransform = 'translate3d(' + (- (x)) + 'px, 0px, 0px)';
let len = this.props.children.length;
idx = this.loopedIndex(idx, len);
this.setState({ loopIdx: idx, offsetX: x });
// 内层child changeleft
this.changeChildPosition(idx, x);
// 确保当前页的前后页都已经具备了
if (state.total < 3) {
return;
}
if (distance && distance < 0) {
idx = this.loopedIndex(idx + 1, len);
this.changeChildPosition(idx, x + width);
} else if (distance && distance > 0) {
idx = this.loopedIndex(idx - 1, len);
this.changeChildPosition(idx, x - width);
}
}
// 改变子元素位置
changeChildPosition(idx, offset) {
if (idx < 0 || idx > this.props.children.length) {
return;
}
let childNum = 'child' + idx;
let childView = findDOMNode(this.refs[childNum]);
childView.style.left = offset + 'px';
}
renderSwipeView(pages) {
let state = this.state;
const style = {
width: state.width + 'px',
height: state.height
};
return state.total > 1 ? (
<SwipeEvent style={[styles.swipeWrapper, style]}
onSwipeBegin={this.onSwipeBegin.bind(this)}
onSwipeEnd={this.onSwipeEnd.bind(this)}
onSwipe={this.onSwipe.bind(this)}
initialVelocityThreshold={this.props.initialVelocityThreshold}
verticalThreshold={this.props.verticalThreshold}
vertical={this.props.vertical}
horizontalThreshold={this.props.horizontalThreshold}>
<View ref="swipeView" style={[styles.swipeStyle, style]}>{pages}</View>
</SwipeEvent>
) : (
<View ref="swipeView" style={[styles.swipeStyle, style]}>{pages}</View>
);
}
onSwipeBegin({ direction, distance, velocity }) {
this.setState({
isSwiping: true
});
// 手势翻页时暂定自动翻页
clearTimeout(this.autoTimer);
}
onSwipe({ direction, distance, velocity }) {
let index = this.state.index,
width = this.state.width,
x = this.state.offsetX,
num = this.props.children.length,
idx = index;
if (!this.props.loop && (index === num - 1 || index === 0)) {
return;
}
let changeX = 0;
if (distance < 0) {
changeX = - (x + Math.abs(distance));
} else {
changeX = - (x - Math.abs(distance));
}
let swipeView = findDOMNode(this.refs.swipeView);
swipeView.style.transform = 'translate3d(' + (changeX) + 'px, 0px, 0px)';
swipeView.style.webkitTransform = 'translate3d(' + (changeX) + 'px, 0px, 0px)';
}
onSwipeEnd({ direction, distance, velocity }) {
// 更加流畅的翻页体验 TODO
this.setState({
isSwiping: true
});
this.updateIndex(distance);
}
updateIndex(distance) {
const { onChange } = this.props;
let that = this;
let state = this.state,
index = state.index,
step = state.width,
num = this.props.children.length;
index = this.changeIndex(distance, index, num);
// fix 左滑index为负数
let idx = distance ? this.loopedIndex(index, num) : this.loopedIndex(index - 1, num);
// 暴露index
onChange && onChange({ index: idx });
this.setState({ index: index });
// 手势改变改变位置
if (distance) {
let x = index * state.width;
this.changeOffset(x, index, distance);
this.setState({
isSwiping: false
});
// 手势翻页结束后基于当前index继续轮播
this.setAutoTimer();
}
}
// 循环改变index (自增、自减)
changeIndex(distance, index, num) {
if (this.props.loop) {
if (distance && distance > 0) {
index = index - 1;
} else {
index = index + 1;
}
} else if (!this.props.loop) {
if (distance && distance > 0 && index !== 0) {
index = index - 1;
} else if (distance && distance < 0 && index !== num - 1) {
index = index + 1;
} else {
return index;
}
}
return index;
}
// 使index维持在0-length之间循环
loopedIndex(idx, len) {
// console.log(idx, len)
if (idx > 0 && idx >= len - 1) {
idx = idx % len;
} else if (idx < 0 && idx <= - (len + 1)) {
idx = idx % len;
idx = idx === 0 ? 0 : idx + len;
} else if (idx < 0 && idx >= - (len + 1)) {
idx = idx + len;
}
return idx;
}
renderPagination() {
let props = this.props;
if (this.state.total <= 1) {
return;
}
let itemSelectedColor, itemColor, itemSize;
if (props.paginationStyle) {
itemSelectedColor = props.paginationStyle.itemSelectedColor ||
styles.defaultPaginationStyle.itemSelectedColor;
itemColor = props.paginationStyle.itemColor ||
styles.defaultPaginationStyle.itemColor;
itemSize = props.paginationStyle.itemSize ||
styles.defaultPaginationStyle.itemSize;
}
const activeStyle = [
styles.activeDot,
{
backgroundColor: itemSelectedColor,
width: itemSize,
height: itemSize,
borderRadius: itemSize,
}
];
const normalStyle = [
styles.normalDot,
{
backgroundColor: itemColor,
width: itemSize,
height: itemSize,
borderRadius: itemSize,
}
];
let dots = [];
let ActiveDot = this.props.activeDot || <View style={activeStyle} />;
let NormalDot = this.props.normalDot || <View style={normalStyle} />;
let activeIndex = this.state.loopIdx;
for (let i = 0; i < this.state.total; i++) {
dots.push(i === activeIndex
? cloneElement(ActiveDot, { key: i })
: cloneElement(NormalDot, { key: i }));
}
return (
<View style={[styles.defaultPaginationStyle, props.paginationStyle ? props.paginationStyle : '']}>
{dots}
</View>
);
}
getPages() {
let state = this.state,
props = this.props,
children = props.children,
index = state.index;
let pages = [];
if (state.total > 1) {
children.forEach((child, i) => {
let refStr = 'child' + i;
let translateStyle = {
width: state.width + 'px',
height: state.height,
left: i * state.width + 'px'
};
pages.push(<View ref={refStr} className={'childWrap' + i} style={[styles.childrenStyle, translateStyle]} key={i}>{this.getChild(i, child)}</View>);
});
} else {
pages = (<View style={styles.childrenStyle}>{children}</View>);
}
return pages;
}
// 只渲染当前index前后两页 for 性能
// TODO 是否有必要删除已经浏览过的page
getChild(i, child) {
let that = this,
index = this.state.index,
len = this.props.children.length;
// 自动翻页时确保翻页之前的当前页的前后结点,防止翻页空白
if (this.props.autoPlay) {
index = index - 1;
}
index = this.loopedIndex(index, len);
let left = this.loopedIndex(index - 1, len),
right = this.loopedIndex(index + 1, len);
if (i === index || i === left || i === right) {
return child;
}
}
render() {
let that = this;
return (
<View style={[styles.slideWrapper, this.props.style]}>
{this.renderSwipeView(that.getPages())}
{this.props.showsPagination ? this.renderPagination() : ''}
</View>
);
}
};
Slide.defaultProps = {
horizontal: true,
showsPagination: true,
loop: true,
autoPlay: false,
autoplayInterval: 3000,
index: 0,
paginationStyle: null,
initialVelocityThreshold: 0.7,
verticalThreshold: 10,
horizontalThreshold: 10,
vertical: false
};
Slide.propTypes = propTypes;