zent
Version:
一套前端设计语言和基于React的实现
291 lines (248 loc) • 7.22 kB
JavaScript
import React, { PureComponent, Component, Children, cloneElement } from 'react';
import WindowResizeHandler from 'utils/component/WindowResizeHandler';
import Icon from 'icon';
import PropTypes from 'prop-types';
import cx from 'classnames';
import forEach from 'lodash/forEach';
import throttle from 'lodash/throttle';
import SwiperDots from './SwiperDots';
function setStyle(target, styles) {
const { style } = target;
Object.keys(styles).forEach(attribute => {
style[attribute] = styles[attribute];
});
}
export default class Swiper extends (PureComponent || Component) {
static PropTypes = {
className: PropTypes.string,
prefix: PropTypes.string,
transitionDuration: PropTypes.number,
autoplay: PropTypes.bool,
autoplayInterval: PropTypes.number,
dots: PropTypes.bool,
dotsColor: PropTypes.string,
dotsSize: PropTypes.oneOf(['normal', 'small', 'large']),
arrows: PropTypes.bool,
arrowsType: PropTypes.oneOf(['dark', 'light']),
onChange: PropTypes.func
};
static defaultProps = {
className: '',
prefix: 'zent',
transitionDuration: 300,
autoplay: false,
autoplayInterval: 3000,
dots: true,
dotsColor: 'black',
dotsSize: 'normal',
arrows: false,
arrowsType: 'dark'
};
state = {
currentIndex: 0
};
init = () => {
const { currentIndex } = this.state;
const innerElements = this.swiperContainer.children;
this.setSwiperWidth();
setStyle(this.swiperContainer, {
width: `${this.swiperWidth * innerElements.length}px`
});
forEach(innerElements, item => {
setStyle(item, {
width: `${100 / innerElements.length}%`
});
});
innerElements.length > 1 && this.translate(currentIndex, true);
};
getSwiper = swiper => {
this.swiper = swiper;
};
getSwiperContainer = swiperContainer => {
this.swiperContainer = swiperContainer;
};
setSwiperWidth = () => {
this.swiperWidth = this.swiper.getBoundingClientRect().width;
};
startAutoplay = () => {
const { autoplayInterval } = this.props;
this.autoplayTimer = setInterval(this.next, Number(autoplayInterval));
};
clearAutoplay = () => {
clearInterval(this.autoplayTimer);
};
next = () => {
const { currentIndex } = this.state;
this.swipeTo(currentIndex + 1);
};
prev = () => {
const { currentIndex } = this.state;
this.swipeTo(currentIndex - 1);
};
swipeTo = index => {
let currentIndex = index;
this.setState({ currentIndex });
};
translate = (currentIndex, isSilent) => {
const { transitionDuration } = this.props;
const initIndex = -1;
const itemWidth = this.swiperWidth;
const translateDistance = itemWidth * (initIndex - currentIndex);
setStyle(this.swiperContainer, {
transform: `translateX(${translateDistance}px)`,
'transition-duration': isSilent ? '0ms' : `${transitionDuration}ms`
});
};
resetPosition = currentIndex => {
const { transitionDuration, children: { length } } = this.props;
if (currentIndex < 0) {
setTimeout(
() =>
this.setState({
currentIndex: length - 1
}),
transitionDuration
);
} else {
setTimeout(
() =>
this.setState({
currentIndex: 0
}),
transitionDuration
);
}
};
getRealPrevIndex = index => {
const { children: { length } } = this.props;
if (index > length - 1) {
return length - 1;
} else if (index < 0) {
return 0;
}
return index;
};
cloneChildren = children => {
const length = Children.count(children);
if (length <= 1) {
return children;
}
const clonedChildren = new Array(length + 2);
Children.forEach(children, (child, index) => {
clonedChildren[index + 1] = child;
if (index === 0) {
clonedChildren[length + 1] = child;
} else if (index === length - 1) {
clonedChildren[0] = child;
}
});
return clonedChildren;
};
handleMouseEnter = () => {
const { autoplay } = this.props;
autoplay && this.clearAutoplay();
};
handleMouseLeave = () => {
const { autoplay } = this.props;
autoplay && this.startAutoplay();
};
handleDotsClick = index => {
this.setState({ currentIndex: index });
};
componentDidMount() {
const { autoplay } = this.props;
autoplay && this.startAutoplay();
this.init();
}
componentDidUpdate(prevProps, prevState) {
const { onChange, children: { length } } = this.props;
const { currentIndex } = this.state;
const prevIndex = prevState.currentIndex;
if (prevIndex === currentIndex) {
return;
}
const isSilent = prevIndex > length - 1 || prevIndex < 0;
this.translate(currentIndex, isSilent);
if (currentIndex > length - 1 || currentIndex < 0) {
return this.resetPosition(currentIndex);
}
onChange && onChange(currentIndex, this.getRealPrevIndex(prevIndex));
}
componentWillUnmount() {
this.clearAutoplay();
}
render() {
const {
className,
prefix,
dots,
dotsColor,
dotsSize,
arrows,
arrowsType,
children
} = this.props;
const { currentIndex } = this.state;
const classString = cx(`${prefix}-swiper`, className, {
[`${prefix}-swiper-light`]: arrows && arrowsType === 'light'
});
const childrenCount = Children.count(children);
const clonedChildren = this.cloneChildren(children);
return (
<div
ref={this.getSwiper}
className={classString}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>
{arrows &&
childrenCount && (
<div
className={`${prefix}-swiper__arrow ${prefix}-swiper__arrow-left`}
onClick={this.prev}
>
<Icon
type="right-circle"
className={`${prefix}-swiper__arrow-icon`}
/>
</div>
)}
{arrows &&
childrenCount > 1 && (
<div
className={`${prefix}-swiper__arrow ${prefix}-swiper__arrow-right`}
onClick={this.next}
>
<Icon
type="right-circle"
className={`${prefix}-swiper__arrow-icon`}
/>
</div>
)}
<div
ref={this.getSwiperContainer}
className={`${prefix}-swiper__container`}
>
{Children.map(clonedChildren, (child, index) => {
return cloneElement(child, {
key: index - 1,
style: { float: 'left', height: '100%' }
});
})}
</div>
{dots &&
childrenCount > 1 && (
<SwiperDots
prefix={prefix}
dotsColor={dotsColor}
dotsSize={dotsSize}
items={children}
currentIndex={currentIndex}
onDotsClick={this.handleDotsClick}
/>
)}
<WindowResizeHandler onResize={throttle(this.init, 1000 / 60)} />
</div>
);
}
}