Home Reference Source Repository

src/components/Slider/Slider.js

import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'

class Slider extends Component {

  constructor (props) {
    super(props)
    this.state = {
      dragStart: 0,
      dragStartTime: new Date(),
      index: 0,
      lastIndex: 0,
      transition: false
    }
  }

  componentWillMount () {
    const { selected } = this.props

    this.setState({
      index: selected,
      lastIndex: selected
    })
  }

  componentWillReceiveProps (nextProps) {
    this.goToSlide(nextProps.selected)
  }

  getDragX (event, isTouch) {
    return isTouch ?
      event.touches[0].pageX :
      event.pageX
  }

  handleDragStart (event, isTouch) {
    const x = this.getDragX(event, isTouch)

    this.setState({
      dragStart: x,
      dragStartTime: new Date(),
      transition: false,
      slideWidth: ReactDOM.findDOMNode(this.refs.slider).offsetWidth
    })
  }

  handleDragMove (event, isTouch) {
    const {
      dragStart,
      lastIndex,
      slideWidth
    } = this.state

    const x = this.getDragX(event, isTouch)
    const offset = dragStart - x
    const percentageOffset = offset / slideWidth
    const newIndex = lastIndex + percentageOffset
    const SCROLL_OFFSET_TO_STOP_SCROLL = 30

    // Stop scrolling if you slide more than 30 pixels
    if (Math.abs(offset) > SCROLL_OFFSET_TO_STOP_SCROLL) {
      event.stopPropagation()
      event.preventDefault()
    }

    this.setState({
      index: newIndex
    })
  }

  handleDragEnd () {
    const {
      children
    } = this.props
    const {
      dragStartTime,
      index,
      lastIndex
    } = this.state

    const timeElapsed = new Date().getTime() - dragStartTime.getTime()
    const offset = lastIndex - index
    const velocity = Math.round(offset / timeElapsed * 10000)

    let newIndex = Math.round(index)

    if (Math.abs(velocity) > 5) {
      newIndex = velocity < 0 ? lastIndex + 1 : lastIndex - 1
    }

    if (newIndex < 0) {
      newIndex = 0
    } else if (newIndex >= children.length) {
      newIndex = children.length - 1
    }

    this.setState({
      dragStart: 0,
      index: newIndex,
      lastIndex: newIndex,
      transition: true
    })
  }

  goToSlide (slideIndex, event) {
    let index = slideIndex
    const {
      children,
      loop
    } = this.props

    if (event) {
      event.preventDefault()
      event.stopPropagation()
    }

    if (index < 0) {
      index = loop ? children.length - 1 : 0
    } else if (index >= children.length) {
      index = loop ? 0 : children.length - 1
    }

    this.setState({
      index: index,
      lastIndex: index,
      transition: true
    })
  }

  renderNav () {
    const { children } = this.props
    const { lastIndex } = this.state

    const nav = children.map((slide, i) => {
      const buttonClasses = i === lastIndex ? 'Slider-navButton Slider-navButton--active' : 'Slider-navButton'
      return (
        <button
          className={ buttonClasses }
          key={ i }
          onClick={ (event) => this.goToSlide(i, event) } />
      )
    })

    return (
      <div className='Slider-nav'>{ nav }</div>
    )
  }

  renderArrows () {
    const {
      children,
      loop,
      showNav
    } = this.props
    const { lastIndex } = this.state
    let arrowsClasses = showNav ? 'Slider-arrows' : 'Slider-arrows Slider-arrows--noNav'
    if(children.length < 2) {
      arrowsClasses = arrowsClasses + ' hide'
    }
    return (
      <div className={ arrowsClasses }>
        { loop || lastIndex > 0 ?
          <button
            className='Slider-arrow Slider-arrow--left'
            onClick={ (event) => this.goToSlide(lastIndex - 1, event) } /> : null }
        { loop || lastIndex < children.length - 1 ?
          <button
            className='Slider-arrow Slider-arrow--right'
            onClick={ (event) => this.goToSlide(lastIndex + 1, event) } /> : null }
      </div>
    )
  }

  render () {
    const {
      children,
      showArrows,
      showNav
    } = this.props

    const {
      index,
      transition
    } = this.state


    const slidesStyles = {
      width: `${ 100 * children.length }%`,
      transform: `translateX(${ -1 * index * (100 / children.length) }%)`
    }
    const slidesClasses = transition ? 'Slider-slides Slider-slides--transition' : 'Slider-slides'

    return (
      <div className='Slider' ref='slider'>
        { showArrows ? this.renderArrows() : null }
        { showNav ? this.renderNav() : null }

        <div
          className='Slider-inner'
          onTouchStart={ (event) => this.handleDragStart(event, true) }
          onTouchMove={ (event) => this.handleDragMove(event, true) }
          onTouchEnd={ () => this.handleDragEnd(true) }>
          <div
            className={ slidesClasses }
            style={ slidesStyles }>
            { children }
          </div>
        </div>
      </div>
    )
  }

}

Slider.propTypes = {
  children: PropTypes.array,
  loop: PropTypes.bool,
  selected: PropTypes.number,
  showArrows: PropTypes.bool,
  showNav: PropTypes.bool
}

Slider.defaultProps = {
  loop: false,
  selected: 0,
  showArrows: true,
  showNav: true
}

export default Slider