import React , { Component , cloneElement , Children , CSSProperties } from 'react' ; import classnames from 'classnames' ; import PropsType from './PropsType' ; import Events from '../utils/events' ; import Drag from '../drag' ; export interface CarouselProps extends PropsType { prefixCls ? : string ; className ? : string ; } export default class Carousel extends Component < CarouselProps , any > { static defaultProps : CarouselProps = { prefixCls : 'za-carousel' , direction : 'left' , height : 160 , loop : false , activeIndex : 0 , animationDuration : 500 , swipeable : true , autoPlay : false , autoPlayIntervalTime : 3000 , moveDistanceRatio : 0.5 , moveTimeSpan : 300 , showPagination : true , } ; private carouselItems ; private moveInterval ; private translateX = 0 ; private translateY = 0 ; constructor ( props ) { super ( props ) ; this . state = { activeIndex : props . activeIndex , activeIndexChanged : false , } ; } componentDidMount ( ) { const { activeIndex } = this . props ; Events . on ( window , 'resize' , this . resize ) ; this . startAutoPlay ( ) ; this . onJumpTo ( activeIndex ) ; } componentDidUpdate ( prevProps ) { const { activeIndex } = this . props ; if ( activeIndex !== prevProps . activeIndex ) { this . onSlideTo ( activeIndex ) ; } } componentWillUnmount ( ) { this . pauseAutoPlay ( ) ; Events . off ( window , 'resize' , this . resize ) ; Events . off ( this . carouselItems , 'webkitTransitionEnd' , this . transitionEnd ) ; Events . off ( this . carouselItems , 'transitionend' , this . transitionEnd ) ; } onSlideTo = ( index ) => { this . onMoveTo ( index , this . props . animationDuration ) ; } ; onJumpTo = ( index ) => { this . onMoveTo ( index , 0 ) ; } ; onMoveTo = ( index , animationDuration ) => { const dom = this . carouselItems ; const { loop , children , onChange } = this . props ; const maxLength = children ! . length ; const previousIndex = this . state . activeIndex ; this . translateX = - dom . offsetWidth * ( index + loop ) ; this . translateY = - dom . offsetHeight * ( index + loop ) ; this . doTransition ( { x : this . translateX , y : this . translateY } , animationDuration ) ; if ( index > maxLength - 1 ) { index = 0 ; } else if ( index < 0 ) { index = maxLength - 1 ; } const activeIndexChanged = previousIndex !== index ; this . setState ( { activeIndex : index , activeIndexChanged , } ) ; if ( typeof onChange === 'function' && activeIndexChanged ) { onChange ( index ) ; } } ; onDragStart = ( ) => { const { swipeable , children } = this . props ; if ( ! swipeable ) { return false ; } const { activeIndex } = this . state ; const maxLength = children ! . length ; if ( activeIndex <= 0 ) { this . onJumpTo ( 0 ) ; } else if ( activeIndex >= maxLength - 1 ) { this . onJumpTo ( maxLength - 1 ) ; } this . pauseAutoPlay ( ) ; } ; onDragMove = ( event , { offsetX , offsetY } ) => { const { swipeable } = this . props ; if ( ! swipeable ) { return false ; } const distanceX = Math . abs ( offsetX ) ; const distanceY = Math . abs ( offsetY ) ; if ( this . isDirectionX ( ) && ( distanceX < 5 || ( distanceX >= 5 && distanceY >= 1.73 * distanceX ) ) ) { return false ; } if ( ! this . isDirectionX ( ) && ( distanceY < 5 || ( distanceY >= 5 && distanceX >= 1.73 * distanceY ) ) ) { return false ; } if ( ! this . props . loop ) { if ( this . isLastIndex ( ) ) { if ( ( this . isDirectionX ( ) && offsetX < 0 ) || ( ! this . isDirectionX ( ) && offsetY < 0 ) ) { return false ; } } if ( this . isFirstIndex ( ) ) { if ( ( this . isDirectionX ( ) && offsetX > 0 ) || ( ! this . isDirectionX ( ) && offsetY > 0 ) ) { return false ; } } } if ( ! Events . supportsPassiveEvents ) { event . preventDefault ( ) ; } this . doTransition ( { x : this . translateX + offsetX , y : this . translateY + offsetY } , 0 ) ; return true ; } ; onDragEnd = ( _event , { offsetX , offsetY , startTime } ) => { const { swipeable } = this . props ; if ( ! swipeable ) { return false ; } if ( ! offsetX && ! offsetY ) { this . startAutoPlay ( ) ; return ; } const { moveDistanceRatio , moveTimeSpan } = this . props ; let { activeIndex } = this . state ; const dom = this . carouselItems ; const timeSpan = new Date ( ) . getTime ( ) - startTime . getTime ( ) ; const ratio = this . isDirectionX ( ) ? Math . abs ( offsetX / dom . offsetWidth ) : Math . abs ( offsetY / dom . offsetHeight ) ; if ( ratio >= moveDistanceRatio ! || timeSpan <= moveTimeSpan ! ) { const action = ( this . isDirectionX ( ) && offsetX > 0 ) || ( ! this . isDirectionX ( ) && offsetY > 0 ) ? 'prev' : 'next' ; activeIndex = action === 'next' ? activeIndex + 1 : activeIndex - 1 ; } this . onSlideTo ( activeIndex ) ; this . startAutoPlay ( ) ; } ; startAutoPlay = ( ) => { const { direction , loop , autoPlay , autoPlayIntervalTime } = this . props ; this . moveInterval = autoPlay && setInterval ( ( ) => { let { activeIndex } = this . state ; const isLeftOrUpDirection = [ 'left' , 'up' ] . indexOf ( direction ! ) > - 1 ; activeIndex = isLeftOrUpDirection ? activeIndex + 1 : activeIndex - 1 ; if ( ! loop && ( isLeftOrUpDirection ? this . isLastIndex ( ) : this . isFirstIndex ( ) ) ) { this . pauseAutoPlay ( ) ; return ; } this . onSlideTo ( activeIndex ) ; } , autoPlayIntervalTime ) ; } ; pauseAutoPlay = ( ) => { if ( this . moveInterval ) { clearInterval ( this . moveInterval ) ; } } ; parseItems = ( props ) => { if ( props . children . length === 0 ) { return ; } const items = [ ] . concat ( props . children ) ; const firstItem = items [ 0 ] ; const lastItem = items [ items . length - 1 ] ; if ( props . loop ) { items . push ( firstItem ) ; items . unshift ( lastItem ) ; } const newItems = React . Children . map ( items , ( element , index ) => { return cloneElement ( element , { key : index , className : classnames ( `${ props . prefixCls }__item` , element . props . className ) , } ) ; } ) ; return newItems ; } ; resize = ( ) => { this . onJumpTo ( this . state . activeIndex ) ; } ; doTransition = ( offset , animationDuration ) => { const dom = this . carouselItems ; let x = 0 ; let y = 0 ; if ( this . isDirectionX ( ) ) { ( { x } = offset ) ; } else { ( { y } = offset ) ; } dom . style . WebkitTransformDuration = `${ animationDuration }ms` ; dom . style . transitionDuration = `${ animationDuration }ms` ; dom . style . WebkitTransform = `translate3d(${ x }px, ${ y }px, 0)` ; dom . style . transform = `translate3d(${ x }px, ${ y }px, 0)` ; } ; transitionEnd = ( ) => { const { onChangeEnd } = this . props ; const { activeIndex , activeIndexChanged } = this . state ; const dom = this . carouselItems ; this . translateX = - dom . offsetWidth * ( activeIndex + this . props . loop ) ; this . translateY = - dom . offsetHeight * ( activeIndex + this . props . loop ) ; this . doTransition ( { x : this . translateX , y : this . translateY } , 0 ) ; if ( typeof onChangeEnd === 'function' && activeIndexChanged ) { onChangeEnd ( activeIndex ) ; } } ; isLastIndex = ( ) => { return this . state . activeIndex >= this . props . children ! . length - 1 ; } ; isFirstIndex = ( ) => { return this . state . activeIndex <= 0 ; } ; isDirectionX = ( ) => { return [ 'left' , 'right' ] . indexOf ( this . props . direction ! ) > - 1 ; } ; renderPaginationItem = ( _result , index ) => { const { prefixCls } = this . props ; const paginationItemCls = classnames ( `${ prefixCls }__pagination__item` , { [ `${ prefixCls }__pagination__item--active` ] : index === this . state . activeIndex , } ) ; return ( < div key = { `pagination-${ index }` } className = { paginationItemCls } onClick = { ( ) => this . onSlideTo ( index ) } />); } ; renderPagination = ( ) => { const { prefixCls , showPagination , children } = this . props ; return ( showPagination && ( < div className = { `${ prefixCls }__pagination` } > { Children . map ( children , this . renderPaginationItem ) } < /div>)); } ; render ( ) { const { prefixCls , className , height , style } = this . props ; const items = this . parseItems ( this . props ) ; const itemsStyle = { } ; const direction = this . isDirectionX ( ) ? 'horizontal' : 'vertical' ; const cls = classnames ( prefixCls , className , `${ prefixCls }--${ direction }` ) ; if ( ! this . isDirectionX ( ) ) { itemsStyle . height = height ; } const content = ( < div ref = { ( ele ) => { this . carouselItems = ele ; } } className = { `${ prefixCls }__items` } onTransitionEnd = { this . transitionEnd } style = { itemsStyle } > { items } < /div>); return ( < div className = { cls } style = { style } > < Drag onDragStart = { this . onDragStart } onDragMove = { this . onDragMove } onDragEnd = { this . onDragEnd } > { content } < /Drag> { this . renderPagination ( ) } < /div>); } }