react-alice-carousel
Version:
React image gallery, react slideshow carousel, react content rotator
886 lines (732 loc) • 30.3 kB
JavaScript
'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;
}