UNPKG

react-alice-carousel

Version:

React image gallery, react slideshow carousel, react content rotator

886 lines (732 loc) 30.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactSwipeable = require('react-swipeable'); var _reactSwipeable2 = _interopRequireDefault(_reactSwipeable); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var AliceCarousel = function (_React$PureComponent) { _inherits(AliceCarousel, _React$PureComponent); function AliceCarousel(props) { _classCallCheck(this, AliceCarousel); var _this = _possibleConstructorReturn(this, (AliceCarousel.__proto__ || Object.getPrototypeOf(AliceCarousel)).call(this, props)); _this.state = { slides: [], currentIndex: 1, duration: props.duration || 250, style: { transition: 'transform 0ms ease-out' } }; _this._slideNext = _this._slideNext.bind(_this); _this._slidePrev = _this._slidePrev.bind(_this); _this._onTouchEnd = _this._onTouchEnd.bind(_this); _this._onTouchMove = _this._onTouchMove.bind(_this); _this._keyUpHandler = _this._keyUpHandler.bind(_this); _this._disableAnimation = _this._disableAnimation.bind(_this); _this._allowAnimation = _this._allowAnimation.bind(_this); _this._getActiveSlideIndex = _this._getActiveSlideIndex.bind(_this); _this._resizeHandler = throttle(_this._resizeHandler, 250).bind(_this); _this._getStageComponentNode = _this._getStageComponentNode.bind(_this); _this._onMouseEnterAutoPlayHandler = _this._onMouseEnterAutoPlayHandler.bind(_this); _this._onMouseLeaveAutoPlayHandler = _this._onMouseLeaveAutoPlayHandler.bind(_this); return _this; } _createClass(AliceCarousel, [{ key: 'componentDidMount', value: function componentDidMount() { this._allowAnimation(); this._setInitialState(); window.addEventListener('resize', this._resizeHandler); if (!this.props.keysControlDisabled) { window.addEventListener('keyup', this._keyUpHandler); } if (this.props.autoPlay) this._play(); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { var currentIndex = this.state.currentIndex; var slideToIndex = nextProps.slideToIndex, duration = nextProps.duration, startIndex = nextProps.startIndex, keysControlDisabled = nextProps.keysControlDisabled, autoPlayActionDisabled = nextProps.autoPlayActionDisabled, autoPlayDirection = nextProps.autoPlayDirection, autoPlayInterval = nextProps.autoPlayInterval, autoPlay = nextProps.autoPlay; if (this.props.duration !== duration) { this.setState({ duration: duration }); } if (currentIndex !== slideToIndex && slideToIndex !== undefined) { var slideNext = slideToIndex === currentIndex + 1; var slidePrev = slideToIndex === currentIndex - 1; slideNext ? this._slideToItem(currentIndex + 1) : slidePrev ? this._slideToItem(currentIndex - 1) : this._slideToItem(slideToIndex); } if (this.props.startIndex !== startIndex && !slideToIndex && slideToIndex !== 0) { this._slideToItem(startIndex); } if (this.props.keysControlDisabled !== keysControlDisabled) { keysControlDisabled === true ? window.removeEventListener('keyup', this._keyUpHandler) : window.addEventListener('keyup', this._keyUpHandler); } if (this.props.autoPlayActionDisabled !== autoPlayActionDisabled || this.props.autoPlayDirection !== autoPlayDirection || this.props.autoPlayInterval !== autoPlayInterval || this.props.autoPlay !== autoPlay) { this._pause(); } } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { if (this.props.autoPlayActionDisabled !== prevProps.autoPlayActionDisabled || this.props.autoPlayDirection !== prevProps.autoPlayDirection || this.props.autoPlayInterval !== prevProps.autoPlayInterval || this.props.autoPlay !== prevProps.autoPlay) { if (this.props.autoPlay) { this._play(); } } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { window.removeEventListener('resize', this._resizeHandler); if (!this.props.keysControlDisabled) { window.removeEventListener('keyup', this._keyUpHandler); } } /** * create clones for carousel items * * @param children * @param itemsInSlide * @returns {object} */ }, { key: '_cloneSlides', value: function _cloneSlides(children, itemsInSlide) { var first = children.slice(0, itemsInSlide); var last = children.slice(children.length - itemsInSlide); return last.concat(children, first); } /** * set initial carousel index * * @param {number} index * @param {number} childrenLength * @returns {number} */ }, { key: '_setStartIndex', value: function _setStartIndex(childrenLength, index) { var startIndex = index ? Math.abs(Math.ceil(index)) : 0; return Math.min(startIndex, childrenLength - 1); } /** * calculate initial props * * @param {object} nextProps props which will be received * @returns {object} */ }, { key: '_calculateInitialProps', value: function _calculateInitialProps(nextProps) { var totalItems = void 0; var _props = this.props, startIndex = _props.startIndex, children = _props.children; if (nextProps) { totalItems = nextProps.responsive; children = nextProps.children; } var items = this._setTotalItemsInSlide(totalItems, children.length); var currentIndex = this._setStartIndex(children.length, startIndex); var itemWidth = this.stageComponent.getBoundingClientRect().width / items; return { items: items, itemWidth: itemWidth, currentIndex: currentIndex, slides: children, clones: this._cloneSlides(children, items), translate3d: -itemWidth * (items + currentIndex) }; } /** * calculate total items in the slide * * @param {number} totalItems total items for the slide * @param {number} childrenLength slides length * @returns {number} */ }, { key: '_setTotalItemsInSlide', value: function _setTotalItemsInSlide(totalItems, childrenLength) { var items = 1; var width = window.innerWidth; if (this.props.responsive) { var responsive = totalItems || this.props.responsive || {}; if (Object.keys(responsive).length) { Object.keys(responsive).forEach(function (key) { if (key < width) items = Math.min(responsive[key].items, childrenLength) || items; }); } } return items; } /** * set initial state * */ }, { key: '_setInitialState', value: function _setInitialState() { this.setState(this._calculateInitialProps()); } /** * window resize handler * */ }, { key: '_resizeHandler', value: function _resizeHandler() { this._setInitialState(); } /** * refers to parent carousel node * * @param {HTMLElement} node * */ }, { key: '_getStageComponentNode', value: function _getStageComponentNode(node) { this.stageComponent = node; } /** * enable carousel animation */ }, { key: '_allowAnimation', value: function _allowAnimation() { this.allowAnimation = true; } /** * disable carousel animation */ }, { key: '_disableAnimation', value: function _disableAnimation() { this.allowAnimation = false; } /** * check MouseEnter event * @returns {boolean} */ }, { key: '_isHovered', value: function _isHovered() { return this.isHovered; } /** * check if need recalculate a slide position * @param {bool} skip */ }, { key: '_checkRecalculation', value: function _checkRecalculation(skip) { var _this2 = this; var _state = this.state, slides = _state.slides, currentIndex = _state.currentIndex, duration = _state.duration; window.setTimeout(function () { var recalculate = !skip && (currentIndex < 0 || currentIndex >= slides.length); recalculate ? _this2._recalculateSlidePosition() : _this2._onSlideChanged(); }, duration); } /** * set new props to the state */ }, { key: '_recalculateSlidePosition', value: function _recalculateSlidePosition() { var _this3 = this; var _state2 = this.state, items = _state2.items, itemWidth = _state2.itemWidth, slides = _state2.slides, currentIndex = _state2.currentIndex; currentIndex = currentIndex < 0 ? slides.length - 1 : 0; this.setState({ currentIndex: currentIndex, translate3d: -itemWidth * (currentIndex === 0 ? items : slides.length - 1 + items), style: { transition: 'transform 0ms ease-out' } }, function () { return _this3._onSlideChanged(); }); } /** * call callback from props */ }, { key: '_onSlideChange', value: function _onSlideChange() { if (this.props.onSlideChange) { this.props.onSlideChange({ item: this.state.currentIndex, slide: this._getActiveSlideIndex() }); } } /** * call callback from props */ }, { key: '_onSlideChanged', value: function _onSlideChanged() { this._allowAnimation(); if (this.props.onSlideChanged) { this.props.onSlideChanged({ item: this.state.currentIndex, slide: this._getActiveSlideIndex() }); } } /** * create xml element * @returns {XML} */ }, { key: '_prevButton', value: function _prevButton() { return _react2.default.createElement( 'div', { className: 'alice-carousel__prev-btn' }, _react2.default.createElement( 'div', { className: 'alice-carousel__prev-btn-wrapper', onMouseEnter: this._onMouseEnterAutoPlayHandler, onMouseLeave: this._onMouseLeaveAutoPlayHandler }, _react2.default.createElement('div', { className: 'alice-carousel__prev-btn-item', onClick: this._slidePrev }) ) ); } /** * create xml element * @returns {XML} */ }, { key: '_nextButton', value: function _nextButton() { return _react2.default.createElement( 'div', { className: 'alice-carousel__next-btn' }, _react2.default.createElement( 'div', { className: 'alice-carousel__next-btn-wrapper', onMouseEnter: this._onMouseEnterAutoPlayHandler, onMouseLeave: this._onMouseLeaveAutoPlayHandler }, _react2.default.createElement('div', { className: 'alice-carousel__next-btn-item', onClick: this._slideNext }) ) ); } /** * calculate active dot navigation index * @returns {number} */ }, { key: '_getActiveSlideIndex', value: function _getActiveSlideIndex() { var _state3 = this.state, slides = _state3.slides, items = _state3.items, currentIndex = _state3.currentIndex; currentIndex = currentIndex + items; var slidesLength = slides.length; var dotsLength = slidesLength % items === 0 ? Math.floor(slidesLength / items) - 1 : Math.floor(slidesLength / items); if (items === 1) { if (currentIndex < items) { return slidesLength - items; } else if (currentIndex > slidesLength) { return 0; } else { return currentIndex - 1; } } else { if (currentIndex === slidesLength + items) { return 0; } else if (currentIndex < items && currentIndex !== 0) { return dotsLength; } else if (currentIndex === 0) { return slidesLength % items === 0 ? dotsLength : dotsLength - 1; } else { return Math.floor(currentIndex / items) - 1; } } } /** * create markdown for dots navigation * @returns {XML} */ }, { key: '_renderDotsNavigation', value: function _renderDotsNavigation() { var _this4 = this; var _state4 = this.state, slides = _state4.slides, items = _state4.items; return _react2.default.createElement( 'ul', { className: 'alice-carousel__dots' }, slides.map(function (item, i) { if (i < slides.length / items) { return _react2.default.createElement('li', { key: i, onClick: function onClick() { return _this4._slideToItem(i * items); }, onMouseEnter: _this4._onMouseEnterAutoPlayHandler, onMouseLeave: _this4._onMouseLeaveAutoPlayHandler, className: 'alice-carousel__dots-item' + (_this4._getActiveSlideIndex() === i ? ' __active' : '') }); } }) ); } /** * create markdown for play/pause button * @returns {XML} */ }, { key: '_renderPlayPauseButton', value: function _renderPlayPauseButton() { var _this5 = this; return _react2.default.createElement( 'div', { className: 'alice-carousel__play-btn', onClick: function onClick() { return _this5._playPauseToggle(); } }, _react2.default.createElement( 'div', { className: 'alice-carousel__play-btn-wrapper' }, _react2.default.createElement('div', { className: 'alice-carousel__play-btn-item' + (this.state.isPlaying ? ' __pause' : '') }) ) ); } /** * set auto-play interval */ }, { key: '_play', value: function _play() { var _this6 = this; var _props2 = this.props, autoPlayDirection = _props2.autoPlayDirection, autoPlayInterval = _props2.autoPlayInterval, duration = _props2.duration; var playInterval = typeof autoPlayInterval === 'number' ? Math.max(autoPlayInterval, duration) : duration; this.setState({ isPlaying: true }); if (!this._autoPlayIntervalId) { this._autoPlayIntervalId = window.setInterval(function () { if (!_this6._isHovered() && _this6._autoPlayIntervalId) { // check interval if force click if (!_this6.allowAnimation) return; autoPlayDirection === 'rtl' ? _this6._slidePrev(false) : _this6._slideNext(false); } }, playInterval); } } /** * clear auto-play interval */ }, { key: '_pause', value: function _pause() { if (this._autoPlayIntervalId) { window.clearInterval(this._autoPlayIntervalId); this._autoPlayIntervalId = null; this.setState({ isPlaying: false }); } } /** * call play/pause methods */ }, { key: '_playPauseToggle', value: function _playPauseToggle() { this.state.isPlaying ? this._pause() : this._play(); } /** * call external methods * @param {Event} e */ }, { key: '_keyUpHandler', value: function _keyUpHandler(e) { if (!this.allowAnimation) return; switch (e.keyCode) { case 32: this._playPauseToggle(); break; case 37: this._slidePrev(); break; case 39: this._slideNext(); break; } } /** * call external methods * @param {bool} action */ }, { key: '_slidePrev', value: function _slidePrev() { var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; if (action && this.props.autoPlayActionDisabled) { this._pause(); } this._slideToItem(this.state.currentIndex - 1); } /** * call external methods * @param {bool} action */ }, { key: '_slideNext', value: function _slideNext() { var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; if (action && this.props.autoPlayActionDisabled) { this._pause(); } this._slideToItem(this.state.currentIndex + 1); } /** * set new props to the state * * @param {number} index * @param {bool} skip * @param {number} duration */ }, { key: '_slideToItem', value: function _slideToItem(index, skip) { var _this7 = this; var duration = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.state.duration; if (!this.allowAnimation) return; this._disableAnimation(); this._onSlideChange(); var _state5 = this.state, items = _state5.items, itemWidth = _state5.itemWidth; var translate = (index + items) * itemWidth; this.setState({ currentIndex: index, translate3d: -translate, style: { transition: 'transform ' + duration + 'ms ease-out' } }, function () { return _this7._checkRecalculation(skip); }); } /** * calculate swipePosition on touchEvent start */ }, { key: '_onTouchMove', value: function _onTouchMove() { this._pause(); if (this.props.swipeDisabled) return; var _state6 = this.state, slides = _state6.slides, items = _state6.items, itemWidth = _state6.itemWidth, translate3d = _state6.translate3d; var maxPosition = (slides.length + items) * itemWidth; var direction = arguments[1] > 0 ? 'LEFT' : 'RIGHT'; var position = translate3d - arguments[1]; if (position >= 0 || Math.abs(position) >= maxPosition) { recalculatePosition(); } setTransformAnimation(this.stageComponent, position); this.swipePosition = { position: position, direction: direction }; function recalculatePosition() { direction === 'RIGHT' ? position += -slides.length * itemWidth : position += maxPosition - items * itemWidth; if (position >= 0 || Math.abs(position) >= maxPosition) { recalculatePosition(); } } } /** * set transformations before touchEvent end */ }, { key: '_beforeTouchEnd', value: function _beforeTouchEnd() { var _this8 = this; var _state7 = this.state, itemWidth = _state7.itemWidth, slides = _state7.slides, items = _state7.items, duration = _state7.duration; var swipePosition = Math.abs(this.swipePosition.position); var swipeIndex = this.swipePosition.direction === 'LEFT' ? Math.floor(swipePosition / itemWidth) + 1 : Math.floor(swipePosition / itemWidth); var transformPosition = -swipeIndex * itemWidth; setTransformAnimation(this.stageComponent, transformPosition, duration); var currentIndex = swipeIndex - items; if (currentIndex === slides.length) { currentIndex = 0; } if (currentIndex < 0) { currentIndex = slides.length + currentIndex; } var position = itemWidth * (currentIndex + items); //(index + items) * itemWidth; setTimeout(function () { setTransformAnimation(_this8.stageComponent, -position); _this8._slideToItem(currentIndex, true, 0); }, duration); } /** * called on touchEvent end */ }, { key: '_onTouchEnd', value: function _onTouchEnd() { if (this.props.swipeDisabled) return; this._beforeTouchEnd(); } /** * called on mouseEnter event for parent carousel node */ }, { key: '_onMouseEnterAutoPlayHandler', value: function _onMouseEnterAutoPlayHandler() { this.isHovered = true; } /** * called on mouseLeave event for parent carousel node */ }, { key: '_onMouseLeaveAutoPlayHandler', value: function _onMouseLeaveAutoPlayHandler() { this.isHovered = false; } }, { key: 'render', value: function render() { var _this9 = this; var style = _extends({}, this.state.style, { transform: 'translate3d(' + this.state.translate3d + 'px, 0, 0)' }); var slides = this.state.clones || this.state.slides; return _react2.default.createElement( 'div', { className: 'alice-carousel' }, _react2.default.createElement( _reactSwipeable2.default, { onSwiping: this._onTouchMove, onSwiped: this._onTouchEnd }, _react2.default.createElement( 'div', { className: 'alice-carousel__wrapper', onMouseEnter: this._onMouseEnterAutoPlayHandler, onMouseLeave: this._onMouseLeaveAutoPlayHandler }, _react2.default.createElement( 'ul', { className: 'alice-carousel__stage', ref: this._getStageComponentNode, style: style }, slides.map(function (item, i) { return _react2.default.createElement( 'li', { className: 'alice-carousel__stage-item', key: i, style: { width: _this9.state.itemWidth + 'px' } }, item ); }) ) ) ), !this.props.buttonsDisabled ? this._prevButton() : null, !this.props.buttonsDisabled ? this._nextButton() : null, !this.props.dotsDisabled ? this._renderDotsNavigation() : null, !this.props.playButtonDisabled ? this._renderPlayPauseButton() : null ); } }]); return AliceCarousel; }(_react2.default.PureComponent); AliceCarousel.propTypes = { children: _propTypes2.default.array.isRequired, onSlideChange: _propTypes2.default.func, onSlideChanged: _propTypes2.default.func, keysControlDisabled: _propTypes2.default.bool, playButtonDisabled: _propTypes2.default.bool, buttonsDisabled: _propTypes2.default.bool, dotsDisabled: _propTypes2.default.bool, swipeDisabled: _propTypes2.default.bool, responsive: _propTypes2.default.object, duration: _propTypes2.default.number, startIndex: _propTypes2.default.number, slideToIndex: _propTypes2.default.number, autoPlay: _propTypes2.default.bool, autoPlayInterval: _propTypes2.default.number, autoPlayDirection: _propTypes2.default.string, autoPlayActionDisabled: _propTypes2.default.bool }; exports.default = AliceCarousel; function setTransformAnimation(element, position) { var durationMs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0; var prefixes = ['Webkit', 'Moz', 'ms', 'O', '']; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = prefixes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var value = _step.value; element.style[value + 'Transform'] = 'translate3d(' + position + 'px, 0, 0)'; element.style[value + 'Transition'] = 'transform ' + durationMs + 'ms ease-out'; } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } } function throttle(func) { var ms = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var savedArgs = void 0, savedThis = void 0, isThrottled = false; function wrapper() { if (isThrottled) { savedArgs = arguments; savedThis = this; return; } func.apply(this, arguments); isThrottled = true; setTimeout(function () { isThrottled = false; if (savedArgs) { wrapper.apply(savedThis, savedArgs); savedArgs = savedThis = null; } }, ms); } return wrapper; }