diff --git a/README.md b/README.md index 5b388d1..14dee02 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ All attributes are optional. - `auto` {Boolean} `true` or `false` (`false` by default) toggle auto sliding. - `interval` {Number} (`4000`ms by default) interval for auto sliding. - `duration` {Number} (`300`ms by default) duration for animation. +- `onStartFrameIndex` {Number} (`0` by default) the index of the frame on first render. - `onTransitionEnd` {Function({ prev: HTMLElement, current: HTMLElement, next: HTMLElement})} on frames transition end callback. - `widgets` {Array of ReactClass} Indicator and switcher could be various, so it's not builtin. Here's some example custom widgets diff --git a/src/carousel.js b/src/carousel.js index 97fac75..f3ab1e0 100644 --- a/src/carousel.js +++ b/src/carousel.js @@ -20,7 +20,7 @@ class Carousel extends React.Component { this.state = { frames: [].concat(props.frames || props.children || []), - current: 0 + currentFrameIndex: props.onStartFrameIndex } this.mounted = false @@ -32,6 +32,7 @@ class Carousel extends React.Component { this.autoSlide = this.autoSlide.bind(this) this.prev = this.prev.bind(this) this.next = this.next.bind(this) + this.setFrame = this.setFrame.bind(this) if (props.loop === false && props.auto) { console.warn('[re-carousel] Auto-slide only works in loop mode.') @@ -46,6 +47,10 @@ class Carousel extends React.Component { this.refs.wrapper.addEventListener('touchmove', this.onTouchMove, {capture: true}) this.refs.wrapper.addEventListener('touchend', this.onTouchEnd, {capture: true}) window.addEventListener('resize', this.onResize); + + if (this.state.currentFrameIndex) { + this.setFrame(this.state.currentFrameIndex); + } } componentWillUnmount () { @@ -69,7 +74,7 @@ class Carousel extends React.Component { const frames = [].concat(nextProps.frames || nextProps.children || []) const nextState = { frames } if (frames.length && frames.length !== prevState.frames.length) { - nextState.current = 0 + nextState.currentFrameIndex = 0 } return nextState } @@ -90,7 +95,7 @@ class Carousel extends React.Component { } onTouchStart (e) { - if (this.state.total < 2) return + if (this.state.frames.length < 2) return; // e.preventDefault() this.clearAutoTimeout() @@ -133,11 +138,11 @@ class Carousel extends React.Component { // when reach frames edge in non-loop mode, reduce drag effect. if (!this.props.loop) { - if (this.state.current === this.state.frames.length - 1) { + if (this.state.currentFrameIndex === this.state.frames.length - 1) { deltaX < 0 && (deltaX /= 3) deltaY < 0 && (deltaY /= 3) } - if (this.state.current === 0) { + if (this.state.currentFrameIndex === 0) { deltaX > 0 && (deltaX /= 3) deltaY > 0 && (deltaY /= 3) } @@ -159,21 +164,21 @@ class Carousel extends React.Component { } decideEndPosition () { - const { deltaX = 0, deltaY = 0, current, frames } = this.state + const { deltaX = 0, deltaY = 0, currentFrameIndex, frames } = this.state const { axis, loop, minMove } = this.props switch (axis) { case 'x': if (loop === false) { - if (current === 0 && deltaX > 0) return 'origin' - if (current === frames.length - 1 && deltaX < 0) return 'origin' + if (currentFrameIndex === 0 && deltaX > 0) return 'origin' + if (currentFrameIndex === frames.length - 1 && deltaX < 0) return 'origin' } if (Math.abs(deltaX) < minMove) return 'origin' return deltaX > 0 ? 'right' : 'left' case 'y': if (loop === false) { - if (current === 0 && deltaY > 0) return 'origin' - if (current === frames.length - 1 && deltaY < 0) return 'origin' + if (currentFrameIndex === 0 && deltaY > 0) return 'origin' + if (currentFrameIndex === frames.length - 1 && deltaY < 0) return 'origin' } if (Math.abs(deltaY) < minMove) return 'origin' return deltaY > 0 ? 'down' : 'up' @@ -182,12 +187,12 @@ class Carousel extends React.Component { } moveFramesBy (deltaX, deltaY) { - const { prev, current, next } = this.state.movingFrames + const { prev, currentFrameIndex, next } = this.state.movingFrames const { frameWidth, frameHeight } = this.state switch (this.props.axis) { case 'x': - translateXY(current, deltaX, 0) + translateXY(currentFrameIndex, deltaX, 0) if (deltaX < 0) { translateXY(next, deltaX + frameWidth, 0) } else { @@ -195,7 +200,7 @@ class Carousel extends React.Component { } break case 'y': - translateXY(current, 0, deltaY) + translateXY(currentFrameIndex, 0, deltaY) if (deltaY < 0) { translateXY(next, 0, deltaY + frameHeight) } else { @@ -239,13 +244,13 @@ class Carousel extends React.Component { } next () { - const { current, frames } = this.state - if (!this.props.loop && current === frames.length - 1) return false + const { currentFrameIndex, frames } = this.state + if (!this.props.loop && currentFrameIndex === frames.length - 1) return false this.autoSlide('next') } prev () { - if (!this.props.loop && this.state.current === 0) return false + if (!this.props.loop && this.state.currentFrameIndex === 0) return false const { prev, next } = this.state.movingFrames if (prev === next) { @@ -262,38 +267,72 @@ class Carousel extends React.Component { this.autoSlide('prev') } + setFrame (index, ms = undefined) { + + if (!ms) { + this.setState({ + currentFrameIndex: index + }) + this.prepareSiblingFrames(true, index); + return; + } + + const diff = Math.abs(index - this.state.currentFrameIndex); + if (index < this.state.currentFrameIndex) { + for (let i = 0; i < diff; i++) { + setTimeout(() => this.prev(), i * ms); + } + } + else if (index > this.state.currentFrameIndex) { + for (let i = 0; i < diff; i++) { + setTimeout(() => this.next(), i * ms); + } + } + } + clearAutoTimeout () { clearTimeout(this.state.slider) } updateFrameSize (cb) { - const { width, height } = window.getComputedStyle(this.refs.wrapper) - this.setState({ - frameWidth: parseFloat(width.split('px')[0]), - frameHeight: parseFloat(height.split('px')[0]) - }, cb) + if (this.refs.wrapper) { + const { width, height } = window.getComputedStyle(this.refs.wrapper) + this.setState({ + frameWidth: parseFloat(width.split('px')[0]), + frameHeight: parseFloat(height.split('px')[0]) + }, cb) + } } getSiblingFrames () { return { - current: this.refs['f' + this.getFrameId()], + currentFrameIndex: this.refs['f' + this.getFrameId()], prev: this.refs['f' + this.getFrameId('prev')], next: this.refs['f' + this.getFrameId('next')] } } - prepareSiblingFrames () { - const siblings = this.getSiblingFrames() + getSiblingFramesByOverride (index) { + return { + currentFrameIndex: this.refs['f' + index], + prev: this.refs['f' + this.getFrameId('prev', index)], + next: this.refs['f' + this.getFrameId('next', index)] + } + } + + prepareSiblingFrames (override = false, index = undefined) { + + const siblings = override ? this.getSiblingFramesByOverride(index) : this.getSiblingFrames() if (!this.props.loop) { - this.state.current === 0 && (siblings.prev = undefined) - this.state.current === this.state.frames.length - 1 && (siblings.next = undefined) + this.state.currentFrameIndex === 0 && (siblings.prev = undefined) + this.state.currentFrameIndex === this.state.frames.length - 1 && (siblings.next = undefined) } this.setState({ movingFrames: siblings }) // prepare frames position - translateXY(siblings.current, 0, 0) + translateXY(siblings.currentFrameIndex, 0, 0) if (this.props.axis === 'x') { translateXY(siblings.prev, -this.state.frameWidth, 0) translateXY(siblings.next, this.state.frameWidth, 0) @@ -305,8 +344,8 @@ class Carousel extends React.Component { return siblings } - getFrameId (pos) { - const { frames, current } = this.state + getFrameId (pos, current=this.state.currentFrameIndex) { + const { frames } = this.state const total = frames.length switch (pos) { case 'prev': @@ -319,33 +358,33 @@ class Carousel extends React.Component { } transitFramesTowards (direction) { - const { prev, current, next } = this.state.movingFrames + const { prev, currentFrameIndex, next } = this.state.movingFrames const { duration, axis, onTransitionEnd } = this.props - let newCurrentId = this.state.current + let newCurrentFrameIndexId = this.state.currentFrameIndex switch (direction) { case 'up': - translateXY(current, 0, -this.state.frameHeight, duration) + translateXY(currentFrameIndex, 0, -this.state.frameHeight, duration) translateXY(next, 0, 0, duration) - newCurrentId = this.getFrameId('next') + newCurrentFrameIndexId = this.getFrameId('next') break case 'down': - translateXY(current, 0, this.state.frameHeight, duration) + translateXY(currentFrameIndex, 0, this.state.frameHeight, duration) translateXY(prev, 0, 0, duration) - newCurrentId = this.getFrameId('prev') + newCurrentFrameIndexId = this.getFrameId('prev') break case 'left': - translateXY(current, -this.state.frameWidth, 0, duration) + translateXY(currentFrameIndex, -this.state.frameWidth, 0, duration) translateXY(next, 0, 0, duration) - newCurrentId = this.getFrameId('next') + newCurrentFrameIndexId = this.getFrameId('next') break case 'right': - translateXY(current, this.state.frameWidth, 0, duration) + translateXY(currentFrameIndex, this.state.frameWidth, 0, duration) translateXY(prev, 0, 0, duration) - newCurrentId = this.getFrameId('prev') + newCurrentFrameIndexId = this.getFrameId('prev') break default: // back to origin - translateXY(current, 0, 0, duration) + translateXY(currentFrameIndex, 0, 0, duration) if (axis === 'x') { translateXY(prev, -this.state.frameWidth, 0, duration) translateXY(next, this.state.frameWidth, 0, duration) @@ -357,11 +396,11 @@ class Carousel extends React.Component { onTransitionEnd && setTimeout(() => onTransitionEnd(this.getSiblingFrames()), duration) - this.setState({ current: newCurrentId }) + this.setState({currentFrameIndex: newCurrentFrameIndexId }) } // debugFrames () { - // console.log('>>> DEBUG-FRAMES: current', this.state.current) + // console.log('>>> DEBUG-FRAMES: currentFrameIndex', this.state.currentFrameIndex) // const len = this.state.frames.length // for (let i = 0; i < len; ++i) { // const ref = this.refs['f' + i] @@ -370,7 +409,7 @@ class Carousel extends React.Component { // } render () { - const { frames, current } = this.state + const { frames, currentFrameIndex } = this.state const { widgets, axis, loop, auto, interval } = this.props const wrapperStyle = objectAssign(styles.wrapper, this.props.style) @@ -384,7 +423,7 @@ class Carousel extends React.Component { onMouseDown={this.onTouchStart} > { frames.map((frame, i) => { - const frameStyle = objectAssign({zIndex: 99 - i}, styles.frame) + const frameStyle = objectAssign({zIndex: 99 - (Math.abs(currentFrameIndex - i))}, styles.frame) return