Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding functionality to set frame index directly #126

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 87 additions & 45 deletions src/carousel.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Carousel extends React.Component {

this.state = {
frames: [].concat(props.frames || props.children || []),
current: 0
currentFrameIndex: props.currentFrameIndex
}

this.mounted = false
Expand All @@ -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.')
Expand All @@ -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.props.currentFrameIndex) {
this.setFrame(this.props.currentFrameIndex);
}
}

componentWillUnmount () {
Expand All @@ -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
}
Expand All @@ -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()
Expand Down Expand Up @@ -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)
}
Expand All @@ -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'
Expand All @@ -182,20 +187,20 @@ 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 {
translateXY(prev, deltaX - frameWidth, 0)
}
break
case 'y':
translateXY(current, 0, deltaY)
translateXY(currentFrameIndex, 0, deltaY)
if (deltaY < 0) {
translateXY(next, 0, deltaY + frameHeight)
} else {
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There would be a series of animation for switching to target frame, which works but not ideal, especially when user jumps between a lot frames. We need to switch to target frame directly. 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I preferred to have the animation and to be able to control the animation time. For a small number of frames setting 'ms' to 0 will visually have the same effect, but for many frames this could be an issue as you say. But I will look into creating a function that allows changing it directly.

Copy link
Author

@liuloppan liuloppan Apr 6, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amio I have updated the PR. Also updated index.js so that the example is using this functionality. Currently currentFrameIndex i passed as prop and is set to 3 to show how it works, but it could be removed.

Now you can chose to have the animation if you put in how many milliseconds (ms) it should animate between. Otherwise (default) is that the state.currentFrameIndex is set as well as state.movingFrames (which has a special override case). Which means we don't need to render any frames in between.

}
}
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)
Expand All @@ -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':
Expand All @@ -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)
Expand All @@ -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]
Expand All @@ -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)

Expand All @@ -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 <div ref={'f' + i} key={i} style={frameStyle}>{frame}</div>
})
}
Expand All @@ -394,10 +433,11 @@ class Carousel extends React.Component {
widgets && [].concat(widgets).map((Widget, i) => (
<Widget
key={i}
index={current}
index={currentFrameIndex}
total={frames.length}
prevHandler={this.prev}
nextHandler={this.next}
setFrameHandler={this.setFrame}
axis={axis} loop={loop} auto={auto} interval={interval} />
))
}
Expand All @@ -409,6 +449,7 @@ class Carousel extends React.Component {
Carousel.propTypes = {
axis: PropTypes.oneOf(['x', 'y']),
auto: PropTypes.bool,
currentFrameIndex: PropTypes.number,
loop: PropTypes.bool,
interval: PropTypes.number,
duration: PropTypes.number,
Expand All @@ -422,6 +463,7 @@ Carousel.propTypes = {
Carousel.defaultProps = {
axis: 'x',
auto: false,
currentFrameIndex: 0,
loop: false,
interval: 5000,
duration: 300,
Expand Down
8 changes: 6 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ class App extends React.Component {
<span className={this.state.axis === 'y' ? 'axis current' : 'axis'}
onClick={this.setAxis('y')}>vertical</span>
</header>
<Carousel loop auto axis={this.state.axis} widgets={[Dots, Buttons]} className="custom-class">
<p style={{backgroundColor: 'royalblue', height: '100%'}}>FRAME 1</p>
<Carousel loop currentFrameIndex={3} axis={this.state.axis} widgets={[Dots, Buttons]} className="custom-class">
<p style={{backgroundColor: 'royalblue', height: '100%'}}>FRAME 1</p>
<p style={{backgroundColor: 'orange', height: '100%'}}>FRAME 2</p>
<p style={{backgroundColor: 'orchid', height: '100%'}}>FRAME 3</p>
<p style={{backgroundColor: 'red', height: '100%'}}>FRAME 4</p>
<p style={{backgroundColor: 'green', height: '100%'}}>FRAME 5</p>
<p style={{backgroundColor: 'blue', height: '100%'}}>FRAME 6</p>
<p style={{backgroundColor: 'pink', height: '100%'}}>FRAME 7</p>
</Carousel>
</div>
)
Expand Down
10 changes: 5 additions & 5 deletions src/indicator-dots.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ function Dot (props) {
return (
<span style={{
display: 'inline-block',
height: '8px',
width: '8px',
borderRadius: '4px',
height: '12px',
width: '12px',
borderRadius: '6px',
backgroundColor: 'white',
margin: '7px 5px',
opacity: props.selected ? '1' : '0.3',
transitionDuration: '300ms'
}} />
}} onClick={props.onClick}/>
)
}

Expand All @@ -32,7 +32,7 @@ export default function IndicatorDots (props) {
return (
<div style={wrapperStyle}>{
Array.apply(null, Array(props.total)).map((x, i) => {
return <Dot key={i} selected={props.index === i} />
return <Dot key={i} selected={props.index === i} onClick={() => {props.setFrameHandler(i)}} />
})
}</div>
)
Expand Down