weex-nuke
Version:
基于 Rax 、Weex 的高性能组件体系 ~~
353 lines (334 loc) • 9.56 kB
JSX
/** @jsx createElement */
'use strict';
import { createElement, Component, findDOMNode, PropTypes } from 'rax';
import View from 'nuke-view';
import { Detection } from 'nuke-ep-utils';
import { connectStyle } from 'nuke-theme-provider';
import Emitter from '../util/emitter';
import Transform from './transform.js';
import stylesProvider from '../styles/index';
import Finger from './finger';
import Nav from './nav';
// 在weex及非android4.4以下的机器中才使用ep绑定
let expressionBinding = {};
try {
expressionBinding = require('@weex-module/expressionBinding');
} catch (e) { }
const formatRem = num => (
`${(
(parseInt(num, 10) * document.documentElement.clientWidth) /
750
).toString()}px`
);
class Tabbar extends Component {
constructor(props) {
super(props);
this.childrenElements = []; // 存放tab主体element
this.tabMap = {}; // 用于记录哪个tab是被激活过的状态
this.tabPage = []; // 存放tab节点
this.ob = null;
this.scrolling = false;
this.maxIndex = (props.children && props.children.length - 1) || 0; // tab的子节点只能是tab.item
this.state = {
curIndex:
props.activeKey &&
props.activeKey >= 0 &&
props.activeKey <= this.maxIndex
? props.active
: 0,
transform: 0,
prevIndex: null,
};
if (
typeof props.activeKey === 'number' &&
props.activeKey >= 0 &&
props.activeKey <= this.maxIndex
) {
this.tabMap[props.activeKey] = true;
}
}
componentWillMount() { }
componentDidMount() {
const { curIndex } = this.state;
this.ob = findDOMNode(`view${curIndex}`);
}
/**
* 横向滑动停止
*/
panEnd = (e) => {
if (newIndex < 0) {
newIndex = 0;
} else if (newIndex > this.maxIndex) {
newIndex = this.maxIndex;
}
if (Detection.iOS) {
setTimeout(() => {
this.slideTo(newIndex, 'pan');
}, 50);
} else {
this.slideTo(newIndex, 'pan');
}
};
onSwipe(evt) {
const { direction } = evt;
let { curIndex } = this.state;
if (this.focused) {
return false;
}
switch (direction) {
case 'Left':
curIndex < this.maxIndex && ++curIndex && this.bindStyle(curIndex);
break;
case 'Right':
curIndex > 0 && curIndex-- && this.bindStyle(curIndex);
break;
}
this.slideTo(curIndex);
}
endAnimation() {
this.list.style.webkitTransition = '0';
// this.ob && this.ob.style && (this.ob.style.webkitTransition = '0');
}
bindStyle(current) {
// this.ob && this.restore();
// this.ob = findDOMNode(`view${current}`);
// if (this.ob && !this.ob.scaleX) {
// Transform(this.ob);
// }
}
restore(rotate = true) {
// this.ob.translateX = this.ob.translateY = 0;
// !!rotate && (this.ob.rotateZ = 0);
// this.ob.scaleX = this.ob.scaleY = 1;
// this.ob.originX = this.ob.originY = 0;
}
onPressMove(evt) {
const { curIndex } = this.state;
this.endAnimation();
if (!this.focused) {
if (
(curIndex === 0 && evt.deltaX > 0) ||
(curIndex === this.maxIndex && evt.deltaX < 0)
) {
this.translateX += evt.deltaX / 3;
this.list.style.transform =
`translateX(${formatRem(this.translateX)})`;
} else {
this.translateX += evt.deltaX;
}
}
evt.preventDefault();
}
/**
* tab切换前的回调函数
* @param index 即将滚动去的位置
* @param type 触发滚动的事件类型
*/
slideTo = (index, type) => {
const time = 300;
if (this.isMoving === true) return;
this.isMoving = true;
const { onChange, beforeSlide, forbidNavScroll } = this.props;
const prevIndex = this.state.curIndex;
const moveEndCallback = () => {
if (typeof onChange === 'function') {
onChange(index, prevIndex, type);
}
this.setState({
curIndex: index,
prevIndex,
});
this.isMoving = false;
};
const navRef = this.refs.nav && this.refs.nav.wrappedInstance;
if (typeof beforeSlide === 'function') {
if (!beforeSlide(index, type)) return;
}
// 选中状态滚动,当只有两个item,无需scroll的时候需要禁止滚动
if (!forbidNavScroll) {
navRef.scrollTo(index);
}
navRef.focusMove(index);
navRef.setState({
curIndex: index,
});
// if (Detection.epEnable) {
// // 高版本系统的手机才使用transform动画
// const animation = require('@weex-module/animation');
// const sliderElement = this.refs.listSlider;
// const dist = 750 * index;
// animation.transition(
// findDOMNode(sliderElement),
// {
// styles: {
// transform: `translateX(-${dist}px)`,
// },
// delay: 0,
// duration: time,
// timingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)', // ['cubic-bezier(0.25, 0.46, 0.45, 0.94)', 'cubic-bezier(0,0,0.25,1)']
// },
// () => {}
// );
// }
this.list.style.webkitTransition = '300ms ease';
this.translateX = -index * (750 + this.props.gap);
this.list.style.transform = `translateX(${formatRem(-index * 750)})`;
moveEndCallback();
};
render() {
const { curIndex, prevIndex } = this.state;
const {
themeStyle,
children,
style,
navTop,
dataSource,
renderNavItem,
navStyle,
renderLoading,
navFocusStyle,
forceRender, // ignore SCU just forceRender
navContentStyle,
} = this.props;
const { slider } = themeStyle;
if (!children.length) {
return null;
}
if (navTop) {
slider.top = navStyle && navStyle.height;
navStyle.top = 0;
delete navStyle.bottom;
} else {
slider.bottom = navStyle && navStyle.height;
navStyle.bottom = 0;
delete navStyle.top;
}
// 根据子节点计算tab偏移
const sliderStyle = [slider];
sliderStyle.push({
width: 750 * children.length,
});
this.translateX = -750 * this.state.curIndex;
sliderStyle.push({
transform: `translateX(${formatRem(-750 * this.state.curIndex)})`,
});
if (this.tabMap[curIndex]) {
this.tabMap[curIndex]++;
} else {
this.tabMap[curIndex] = 1;
}
let formatChildren = [];
formatChildren = children.map((child, index) => {
const length = children.length;
const next = circleIndex(this.state.curIndex + 1, length);
const prev = circleIndex(this.state.curIndex - 1, length);
const shouldRender = this.tabMap[index] === 1;
const eachTabStyle = [themeStyle.eachTab, { left: 750 * index }];
if (this.tabMap[index] && curIndex === index) {
this.tabPage[curIndex] = (
<View style={eachTabStyle} key={index} shouldRender={shouldRender}>
{child}
</View>
);
}
if (this.tabMap[index]) {
return this.tabPage[index];
}
if (
typeof renderLoading === 'function' &&
(index === next || index === prev)
) {
return (
<View style={eachTabStyle} key={index}>
{renderLoading(index)}
</View>
);
}
return null;
});
// } else {
// formatChildren = children.map((child, index) => {
// child.props.id = `view${index}`;
// if (index === curIndex) {
// return child;
// }
// return null;
// });
// }
// const childrenElements = [
// <Finger
// onPressMove={this.onPressMove.bind(this)}
// onSwipe={this.onSwipe.bind(this)}
// >
// <View
// style={sliderStyle}
// id="listWrapper"
// ref={node => {
// this.list = findDOMNode(node);
// }}
// onHorizontalPan={
// Detection.epEnable && this.props.epEnable
// ? this.onHorizontalPan
// : null
// }
// >
// {formatChildren}
// </View>
// </Finger>,
// ];
const childrenElements = [
<View
style={sliderStyle}
id="listWrapper"
ref={(node) => {
this.list = findDOMNode(node);
}}
onHorizontalPan={
Detection.epEnable && this.props.epEnable
? this.onHorizontalPan
: null
}
>
{formatChildren}
</View>,
];
const headerStyle = navTop
? {
...navStyle,
top: 0,
positon: 'relative',
}
: navStyle;
const NavWithProps = (
<View style={[themeStyle.header, headerStyle]}>
<Nav
ref="nav"
dataSource={dataSource}
renderNavItem={renderNavItem}
slideTo={(...args) => this.slideTo(...args)}
style={navStyle}
contentStyle={navContentStyle}
navFocusStyle={navFocusStyle}
forceRender={forceRender}
/>
</View>
);
navTop
? childrenElements.unshift(NavWithProps)
: childrenElements.push(NavWithProps);
return (
<View style={[themeStyle.wrapContainer, style]}>{childrenElements}</View>
);
}
}
function circleIndex(i, len) {
return (len + (i % len)) % len;
}
// Tabbar.propTypes = {
// };
Tabbar.defaultProps = {
epEnable: false,
navTop: true,
};
Tabbar.displayName = 'Ep-Tabbar';
export default connectStyle(stylesProvider)(Tabbar);