UNPKG

react-siema-vf

Version:

ReactSiema is a lightweight carousel plugin for React. It's a wrapper based on decent library [Siema](https://github.com/pawelgrzybek/siema).

288 lines (250 loc) 8.98 kB
import React, { Component, PropTypes } from 'react'; import debounce from './utils/debounce'; import transformProperty from './utils/transformProperty'; class ReactSiema extends Component { static propTypes = { resizeDebounce: PropTypes.number, duration: PropTypes.number, easing: PropTypes.string, perPage: PropTypes.oneOfType([ PropTypes.number, PropTypes.object ]), startIndex: PropTypes.number, draggable: PropTypes.bool, threshold: PropTypes.number, loop: PropTypes.bool, children: PropTypes.oneOfType([ PropTypes.element, PropTypes.arrayOf(PropTypes.element) ]), onInit: PropTypes.func, onChange: PropTypes.func, }; events = [ 'onTouchStart', 'onTouchEnd', 'onTouchMove', 'onMouseDown', 'onMouseUp', 'onMouseLeave', 'onMouseMove' ]; constructor(props) { super(); this.config = Object.assign({}, { resizeDebounce: 250, duration: 200, easing: 'ease-out', perPage: 1, startIndex: 0, draggable: true, threshold: 20, loop: false, onInit: () => {}, onChange: () => {}, }, props); this.events.forEach((handler) => { this[handler] = this[handler].bind(this); }); } componentDidMount() { this.config.selector = this.selector; this.currentSlide = this.config.startIndex; this.init(); this.onResize = debounce(() => { this.resize(); this.slideToCurrent(); }, this.config.resizeDebounce); window.addEventListener('resize', this.onResize); if (this.config.draggable) { this.pointerDown = false; this.drag = { start: 0, end: 0, }; } } componentDidUpdate() { this.init(); } componentWillUnmount() { window.removeEventListener('resize', this.onResize); } init() { this.setSelectorWidth(); this.setInnerElements(); this.resolveSlidesNumber(); this.setStyle(this.sliderFrame, { width: `${(this.selectorWidth / this.perPage) * this.innerElements.length}px`, webkitTransition: `all ${this.config.duration}ms ${this.config.easing}`, transition: `all ${this.config.duration}ms ${this.config.easing}` }); for (let i = 0; i < this.innerElements.length; i++) { this.setStyle(this.innerElements[i], { width: `${100 / this.innerElements.length}%` }); } this.slideToCurrent(); this.config.onInit.call(this); } setSelectorWidth() { this.selectorWidth = this.selector.getBoundingClientRect().width; } setInnerElements() { this.innerElements = [].slice.call(this.sliderFrame.children); } resolveSlidesNumber() { if (typeof this.config.perPage === 'number') { this.perPage = this.config.perPage; } else if (typeof this.config.perPage === 'object') { this.perPage = 1; for (let viewport in this.config.perPage) { if (window.innerWidth > viewport) { this.perPage = this.config.perPage[viewport]; } } } } prev() { if (this.currentSlide === 0 && this.config.loop) { this.currentSlide = this.innerElements.length - this.perPage; } else { this.currentSlide = Math.max(this.currentSlide - 1, 0); } this.slideToCurrent(); this.config.onChange.call(this); } next() { if (this.currentSlide === this.innerElements.length - this.perPage && this.config.loop) { this.currentSlide = 0; } else { this.currentSlide = Math.min(this.currentSlide + 1, this.innerElements.length - this.perPage); } this.slideToCurrent(); this.config.onChange.call(this); } goTo(index) { this.currentSlide = Math.min(Math.max(index, 0), this.innerElements.length - 1); this.slideToCurrent(); this.config.onChange.call(this); } slideToCurrent() { this.currentSlide = Math.round(this.currentSlide) const ratio = (this.perPage%Math.floor(this.perPage))/2 const currentSlide = this.currentSlide - ratio < 0 ? 0 : this.currentSlide - ratio this.sliderFrame.style[transformProperty] = `translate3d(-${Math.round(currentSlide * (this.selectorWidth / this.perPage))}px, 0, 0)`; } updateAfterDrag() { const movement = this.drag.end - this.drag.start; if (movement > 0 && Math.abs(movement) > this.config.threshold) { this.prev(); } else if (movement < 0 && Math.abs(movement) > this.config.threshold) { this.next(); } this.slideToCurrent(); } resize() { this.resolveSlidesNumber(); this.selectorWidth = this.selector.getBoundingClientRect().width; this.setStyle(this.sliderFrame, { width: `${(this.selectorWidth / this.perPage) * this.innerElements.length}px` }); } clearDrag() { this.drag = { start: 0, end: 0, }; } setStyle(target, styles) { Object.keys(styles).forEach((attribute) => { target.style[attribute] = styles[attribute]; }); } onTouchStart(e) { e.stopPropagation(); this.pointerDown = true; this.drag.start = e.touches[0].pageX; } onTouchEnd(e) { e.stopPropagation(); this.pointerDown = false; this.setStyle(this.sliderFrame, { webkitTransition: `all ${this.config.duration}ms ${this.config.easing}`, transition: `all ${this.config.duration}ms ${this.config.easing}` }); if (this.drag.end) { this.updateAfterDrag(); } this.clearDrag(); } onTouchMove(e) { e.stopPropagation(); if (this.pointerDown) { this.drag.end = e.touches[0].pageX; this.setStyle(this.sliderFrame, { webkitTransition: `all 0ms ${this.config.easing}`, transition: `all 0ms ${this.config.easing}`, [transformProperty]: `translate3d(${(this.currentSlide * (this.selectorWidth / this.perPage) + (this.drag.start - this.drag.end)) * -1}px, 0, 0)` }); } } onMouseDown(e) { e.preventDefault(); e.stopPropagation(); this.pointerDown = true; this.drag.start = e.pageX; } onMouseUp(e) { e.stopPropagation(); this.pointerDown = false; this.setStyle(this.sliderFrame, { cursor: '-webkit-grab', webkitTransition: `all ${this.config.duration}ms ${this.config.easing}`, transition: `all ${this.config.duration}ms ${this.config.easing}` }); if (this.drag.end) { this.updateAfterDrag(); } this.clearDrag(); } onMouseMove(e) { e.preventDefault(); if (this.pointerDown) { this.drag.end = e.pageX; this.setStyle(this.sliderFrame, { cursor: '-webkit-grabbing', webkitTransition: `all 0ms ${this.config.easing}`, transition: `all 0ms ${this.config.easing}`, [transformProperty]: `translate3d(${(this.currentSlide * (this.selectorWidth / this.perPage) + (this.drag.start - this.drag.end)) * -1}px, 0, 0)` }); } } onMouseLeave(e) { if (this.pointerDown) { this.pointerDown = false; this.drag.end = e.pageX; this.setStyle(this.sliderFrame, { cursor: '-webkit-grab', webkitTransition: `all ${this.config.duration}ms ${this.config.easing}`, transition: `all ${this.config.duration}ms ${this.config.easing}` }); this.updateAfterDrag(); this.clearDrag(); } } render() { return ( <div ref={(selector) => this.selector = selector} style={{ overflow: 'hidden' }} {...this.events.reduce((props, event) => Object.assign({}, props, { [event]: this[event] }), {})} > <div ref={(sliderFrame) => this.sliderFrame = sliderFrame}> {React.Children.map(this.props.children, (children, index) => React.cloneElement(children, { key: index, style: { float: 'left' } }) )} </div> </div> ); } } export default ReactSiema;