/// /// import * as React from 'react'; import classNames from 'classnames'; import Hammer from 'react-hammerjs'; import Slider from '../slider'; import { setTranslate } from '../util/animation'; import { deviceWidth, deviceHeight } from '../util/device'; const prefix = 'tsp-component-carousel'; class Carousel extends React.Component { constructor(props: TspComponentCarouselProps, state: TspComponentCarouselState) { super(props, state); this.onPanStart = this.onPanStart.bind(this); this.onPan = this.onPan.bind(this); this.onPanEnd = this.onPanEnd.bind(this); this.onTap = this.onTap.bind(this); this.onTransitionEnd = this.onTransitionEnd.bind(this); } public static defaultProps: TspComponentCarouselProps = { width: deviceWidth, height: deviceHeight, direction: 'horizontal', refreshId: 1, selectedIndex: 0, swipeable: true }; /** * Slider的实例 */ private slider: Slider; /** * 滑动容器 */ private sliderElem: HTMLElement; /** * 主容器Element */ private containerElem: HTMLElement; /** * dots Element */ private dotsElem: HTMLElement; /** * 自动轮播定时器对象 */ private autoplayInterval: number; /** 是否是自动轮播触发 */ private isAuto: boolean; public componentDidMount(): void { this.sliderElem = this.refs['slider'] as HTMLElement; this.containerElem = this.refs['container'] as HTMLElement; this.dotsElem = this.refs['dots'] as HTMLElement; this.slider = new Slider({ sliderElem: this.sliderElem, containerElem: this.containerElem, count: React.Children.toArray(this.props.children).length, direction: this.props.direction, width: this.props.width, height: this.props.height, selectedIndex: this.props.selectedIndex }); this.carouselInitial(this.props.selectedIndex); this.setContainerHeight(); this.autoplay(); this.sliderElem.addEventListener('webkitTransitionEnd', this.onTransitionEnd); } public componentWillUnmount(): void { this.clearAtuoplay(); this.sliderElem.removeEventListener('webkitTransitionEnd', this.onTransitionEnd); } public componentWillReceiveProps(nextProps: TspComponentCarouselProps): void { const selectedIndex = this.props.selectedIndex; const nextSelectedIndex = nextProps.selectedIndex; const selectedIndexChange = selectedIndex !== nextSelectedIndex; const updateIdChange = this.props.refreshId !== nextProps.refreshId; const changeSelectedIndexType = selectedIndexChange ? '' : 'auto'; if (selectedIndexChange && !updateIdChange) { this.slider.count = React.Children.toArray(nextProps['children']).length; this.changeSelectedIndex(selectedIndex, nextSelectedIndex); } else if (nextProps.refreshId === nextSelectedIndex) { this.setCarouselSort(selectedIndex, nextSelectedIndex); } } /** * 开始滑动 */ public onPanStart(evt: TspComponentSliderEvt): void { evt.preventDefault(); if (!this.props.swipeable) { return; } this.slider.panStart(); this.clearAtuoplay(); } /** * 滑动中 */ public onPan(evt: TspComponentSliderEvt): void { evt.preventDefault(); if (!this.props.swipeable) { return; } this.slider.panMove(evt); } /** * 滑动结束 */ public onPanEnd(evt: TspComponentSliderEvt): void { evt.preventDefault(); if (this.slider.isPanEnd) { return; } this.containerElem.classList.add('tsp-compoent-slider-disabled'); this.slider.addPanEndClass(); this.slider.panEnd(evt); this.setContainerHeight(); this.autoplay(); } public onTap(): void { if (this.props.onClick) { this.props.onClick(this.slider.selectedIndex); } } /** * 监听translate完成事件 */ public onTransitionEnd(): void { const { beforeSelectIndex, afterSelectedIndex } = this.slider.afterChangeIndexs; this.carouselInitial(afterSelectedIndex); if (this.props.dots) { this.dotsElem.children[beforeSelectIndex].classList.remove(`${prefix}-dots-item-current`); this.dotsElem.children[afterSelectedIndex].classList.add(`${prefix}-dots-item-current`); } if (this.props.afterChange) { this.props.afterChange(this.slider.afterChangeIndexs, this.isAuto); } this.isAuto = false; this.slider.removePanEndClass(); this.containerElem.classList.remove('tsp-compoent-slider-disabled'); } /** * 将待分配的children移动到排列的位置 */ public carouselInitial(selectedIndex: number): void { const position = this.slider.getMoveBeforePosition(); this.setCarouselSort(selectedIndex); setTranslate(this.sliderElem, position.x, position.y); } /** * 移动后将容器高度设置为当前子元素的高度 */ public setContainerHeight(isReceive: boolean = false): void { if (this.props.autoHeight) { const index = isReceive ? 0 : 1; const sliderChildrenElem = this.slider.sliderElem.children[index]; if (this.slider.sliderElem.style.height !== sliderChildrenElem.clientHeight + 'px') { this.slider.sliderElem.style.height = sliderChildrenElem.clientHeight + 'px'; } } } /** * 设置要滑动内容的顺序 */ public setCarouselSort(selectedIndex: number, oldSelectedIndex?: number, isReceive: boolean = false): void { const children = this.refs['children']['cloneNode'](true).children; const childrenElemLength = children.length; const fragment = document.createDocumentFragment(); let [prevElem, currentElem, nextElem] = [undefined, undefined, undefined]; let i; for (i = 0; i < childrenElemLength; i++) { if (this.props.direction === 'horizontal') { children[i].style.width = this.props.width + 'px'; } else { children[i].style.height = this.props.height + 'px'; } children[i].classList.add(`${prefix}-slider-item`); } if (isReceive) { prevElem = children[selectedIndex]; currentElem = children[oldSelectedIndex]; nextElem = children[selectedIndex]; } else { if (childrenElemLength > 1) { if (selectedIndex === 0) { prevElem = children[childrenElemLength - 1]; nextElem = children[1]; } else if (selectedIndex === childrenElemLength - 1) { prevElem = children[childrenElemLength - 2]; nextElem = children[0]; } else { prevElem = children[selectedIndex - 1]; nextElem = children[selectedIndex + 1]; } } else if (childrenElemLength === 1) { prevElem = children[0]; nextElem = children[0]; } currentElem = children[selectedIndex]; } if (childrenElemLength >= 1) { fragment.appendChild(prevElem.cloneNode(true)); } if (childrenElemLength === 1) { fragment.appendChild(currentElem.cloneNode(true)); } else { fragment.appendChild(currentElem); } if (childrenElemLength >= 1) { fragment.appendChild(nextElem); } this.sliderElem.innerHTML = null; this.sliderElem.appendChild(fragment); } /** * 改变索引 */ public changeSelectedIndex(selectedIndex: number, nextSelectedIndex: number): void { this.setCarouselSort(nextSelectedIndex, selectedIndex, true); this.setContainerHeight(true); this.containerElem.classList.add('tsp-compoent-slider-disabled'); setTimeout(() => { this.slider.onChangeSelectedIndex({ deltaX: this.isAuto ? -1 : selectedIndex - nextSelectedIndex, deltaY: selectedIndex - nextSelectedIndex, preventDefault: () => null }, nextSelectedIndex); }, 0); } /** * 自动轮播 */ public autoplay(): void { if (this.props.autoplayInterval) { this.autoplayInterval = setInterval(() => { const selectedIndex = this.slider.selectedIndex; const nextSelectedIndex = selectedIndex + 1 >= this.slider.count ? 0 : selectedIndex + 1; this.isAuto = true; this.changeSelectedIndex(selectedIndex, nextSelectedIndex); }, this.props.autoplayInterval); } } /** * 清除自动轮播 */ public clearAtuoplay(): void { if (this.autoplayInterval !== undefined) { clearInterval(this.autoplayInterval); } } public render(): JSX.Element { const model = this.props.direction === 'horizontal' ? 'width' : 'height'; const contentStyle = { [model]: this.props.width }; const flexStyle = { [model]: this.props.width * 3 }; return (
{this.props.children}
{this.props.leftArrow ? (
{this.props.leftArrow}
) : null} {this.props.rightArrow ? (
{this.props.leftArrow}
) : null} {this.props.dots ? (
{React.Children.toArray(this.props.children).map((value, i) => (
))}
) : null}
); } } export default Carousel;