UNPKG

weex-nuke

Version:

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

425 lines (359 loc) 11.3 kB
/* @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;