nuorder-carousel
Version:
React Responsive Carousel
619 lines (507 loc) • 22.9 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 _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _cssClasses = require('../cssClasses');
var _cssClasses2 = _interopRequireDefault(_cssClasses);
var _CSSTranslate = require('../CSSTranslate');
var _CSSTranslate2 = _interopRequireDefault(_CSSTranslate);
var _reactEasySwipe = require('react-easy-swipe');
var _reactEasySwipe2 = _interopRequireDefault(_reactEasySwipe);
var _Thumbs = require('./Thumbs');
var _Thumbs2 = _interopRequireDefault(_Thumbs);
var _customPropTypes = require('../customPropTypes');
var customPropTypes = _interopRequireWildcard(_customPropTypes);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
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 noop = function noop() {};
var defaultStatusFormatter = function defaultStatusFormatter(current, total) {
return current + ' of ' + total;
};
var Carousel = function (_Component) {
_inherits(Carousel, _Component);
function Carousel(props) {
_classCallCheck(this, Carousel);
var _this = _possibleConstructorReturn(this, (Carousel.__proto__ || Object.getPrototypeOf(Carousel)).call(this, props));
_this.autoPlay = function () {
_this.timer = setTimeout(function () {
_this.increment();
}, _this.props.interval);
};
_this.clearAutoPlay = function () {
if (!_this.props.autoPlay) {
return;
}
clearTimeout(_this.timer);
};
_this.resetAutoPlay = function () {
_this.clearAutoPlay();
_this.autoPlay();
};
_this.stopOnHover = function () {
_this.clearAutoPlay();
};
_this.navigateWithKeyboard = function (e) {
var axis = _this.props.axis;
var isHorizontal = axis === 'horizontal';
var nextKey = isHorizontal ? 'ArrowRight' : 'ArrowDown';
var prevKey = isHorizontal ? 'ArrowLeft' : 'ArrowUp';
if (nextKey === e.key) {
_this.increment();
} else if (prevKey === e.key) {
_this.decrement();
}
};
_this.updateSizes = function () {
if (!_this.state.initialized) {
return;
}
var isHorizontal = _this.props.axis === 'horizontal';
var firstItem = _this.refs.item0;
var itemSize = isHorizontal ? firstItem.clientWidth : firstItem.clientHeight;
_this.setState({
itemSize: itemSize,
wrapperSize: isHorizontal ? itemSize * _this.props.children.length : itemSize
});
};
_this.setMountState = function () {
_this.setState({ hasMount: true });
_this.updateSizes();
};
_this.handleClickItem = function (index, item) {
if (_this.state.cancelClick) {
_this.setState({
cancelClick: false
});
return;
}
_this.props.onClickItem(index, item);
if (index !== _this.state.selectedItem) {
_this.setState({
selectedItem: index
});
}
};
_this.handleOnChange = function (index, item) {
_this.props.onChange(index, item);
};
_this.handleClickThumb = function (index, item) {
_this.props.onClickThumb(index, item);
_this.selectItem({
selectedItem: index
});
};
_this.onSwipeStart = function () {
_this.setState({
swiping: true
});
_this.clearAutoPlay();
};
_this.onSwipeEnd = function () {
_this.setState({
swiping: false
});
_this.autoPlay();
};
_this.onSwipeMove = function (delta) {
var list = _reactDom2.default.findDOMNode(_this.refs.itemList);
var isHorizontal = _this.props.axis === 'horizontal';
var initialBoundry = 0;
var currentPosition = -_this.state.selectedItem * 100;
var finalBoundry = -(_this.props.children.length - 1) * 100;
var axisDelta = isHorizontal ? delta.x : delta.y;
var handledDelta = axisDelta;
// prevent user from swiping left out of boundaries
if (currentPosition === initialBoundry && axisDelta > 0) {
handledDelta = 0;
}
// prevent user from swiping right out of boundaries
if (currentPosition === finalBoundry && axisDelta < 0) {
handledDelta = 0;
}
var position = currentPosition + 100 / (_this.state.itemSize / handledDelta) + '%';
['WebkitTransform', 'MozTransform', 'MsTransform', 'OTransform', 'transform', 'msTransform'].forEach(function (prop) {
list.style[prop] = (0, _CSSTranslate2.default)(position, _this.props.axis);
});
// allows scroll if the swipe was within the tolerance
var hasMoved = Math.abs(axisDelta) > _this.props.swipeScrollTolerance;
if (hasMoved && !_this.state.cancelClick) {
_this.setState({
cancelClick: true
});
}
return hasMoved;
};
_this.decrement = function (positions) {
_this.moveTo(_this.state.selectedItem - (typeof positions === 'Number' ? positions : 1));
};
_this.increment = function (positions) {
_this.moveTo(_this.state.selectedItem + (typeof positions === 'Number' ? positions : 1));
};
_this.moveTo = function (position) {
var lastPosition = _this.props.children.length - 1;
if (position < 0) {
position = _this.props.infiniteLoop ? lastPosition : 0;
}
if (position > lastPosition) {
position = _this.props.infiniteLoop ? 0 : lastPosition;
}
_this.selectItem({
// if it's not a slider, we don't need to set position here
selectedItem: position
});
if (_this.props.autoPlay) {
_this.resetAutoPlay();
}
};
_this.changeItem = function (e) {
var newIndex = e.target.value;
_this.selectItem({
selectedItem: newIndex
});
};
_this.selectItem = function (state) {
_this.setState(state);
_this.handleOnChange(state.selectedItem, _this.props.children[state.selectedItem]);
};
_this.getInitialImage = function () {
var selectedItem = _this.props.selectedItem;
var item = _this.refs['item' + selectedItem];
var images = item && item.getElementsByTagName('img');
return images && images[selectedItem];
};
_this.getVariableImageHeight = function (position) {
var item = _this.refs['item' + position];
var images = item && item.getElementsByTagName('img');
if (_this.state.hasMount && images.length > 0) {
var image = images[0];
if (!image.complete) {
// if the image is still loading, the size won't be available so we trigger a new render after it's done
var onImageLoad = function onImageLoad() {
_this.forceUpdate();
image.removeEventListener('load', onImageLoad);
};
image.addEventListener('load', onImageLoad);
}
var height = image.clientHeight;
return height > 0 ? height : null;
}
return null;
};
_this.state = {
initialized: false,
selectedItem: props.selectedItem,
hasMount: false
};
return _this;
}
_createClass(Carousel, [{
key: 'componentDidMount',
value: function componentDidMount() {
if (!this.props.children) {
return;
}
this.setupCarousel();
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
if (nextProps.selectedItem !== this.state.selectedItem) {
this.updateSizes();
this.setState({
selectedItem: nextProps.selectedItem
});
}
if (nextProps.autoPlay !== this.props.autoPlay) {
if (nextProps.autoPlay) {
this.setupAutoPlay();
} else {
this.destroyAutoPlay();
}
}
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate(prevProps) {
if (!prevProps.children && this.props.children && !this.state.initialized) {
this.setupCarousel();
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this.destroyCarousel();
}
}, {
key: 'setupCarousel',
value: function setupCarousel() {
this.bindEvents();
if (this.props.autoPlay) {
this.setupAutoPlay();
}
this.setState({
initialized: true
});
var initialImage = this.getInitialImage();
if (initialImage) {
// if it's a carousel of images, we set the mount state after the first image is loaded
initialImage.addEventListener('load', this.setMountState);
} else {
this.setMountState();
}
}
}, {
key: 'destroyCarousel',
value: function destroyCarousel() {
if (this.state.initialized) {
this.unbindEvents();
this.destroyAutoPlay();
}
}
}, {
key: 'setupAutoPlay',
value: function setupAutoPlay() {
this.autoPlay();
var carouselWrapper = this.refs['carouselWrapper'];
if (this.props.stopOnHover && carouselWrapper) {
carouselWrapper.addEventListener('mouseenter', this.stopOnHover);
carouselWrapper.addEventListener('mouseleave', this.autoPlay);
}
}
}, {
key: 'destroyAutoPlay',
value: function destroyAutoPlay() {
this.clearAutoPlay();
var carouselWrapper = this.refs['carouselWrapper'];
if (this.props.stopOnHover && carouselWrapper) {
carouselWrapper.removeEventListener('mouseenter', this.stopOnHover);
carouselWrapper.removeEventListener('mouseleave', this.autoPlay);
}
}
}, {
key: 'bindEvents',
value: function bindEvents() {
// as the widths are calculated, we need to resize
// the carousel when the window is resized
window.addEventListener("resize", this.updateSizes);
// issue #2 - image loading smaller
window.addEventListener("DOMContentLoaded", this.updateSizes);
if (this.props.useKeyboardArrows) {
document.addEventListener("keydown", this.navigateWithKeyboard);
}
}
}, {
key: 'unbindEvents',
value: function unbindEvents() {
// removing listeners
window.removeEventListener("resize", this.updateSizes);
window.removeEventListener("DOMContentLoaded", this.updateSizes);
var initialImage = this.getInitialImage();
if (initialImage) {
initialImage.removeEventListener("load", this.setMountState);
}
if (this.props.useKeyboardArrows) {
document.removeEventListener("keydown", this.navigateWithKeyboard);
}
}
}, {
key: 'renderItems',
value: function renderItems() {
var _this2 = this;
return _react2.default.Children.map(this.props.children, function (item, index) {
var hasMount = _this2.state.hasMount;
var itemClass = _cssClasses2.default.ITEM(true, index === _this2.state.selectedItem);
return _react2.default.createElement(
'li',
{ ref: "item" + index, key: "itemKey" + index, className: itemClass,
onClick: _this2.handleClickItem.bind(_this2, index, item) },
item
);
});
}
}, {
key: 'renderControls',
value: function renderControls() {
var _this3 = this;
if (!this.props.showIndicators) {
return null;
}
return _react2.default.createElement(
'ul',
{ className: 'control-dots' },
_react2.default.Children.map(this.props.children, function (item, index) {
return _react2.default.createElement('li', { className: _cssClasses2.default.DOT(index === _this3.state.selectedItem), onClick: _this3.changeItem, value: index, key: index });
})
);
}
}, {
key: 'renderStatus',
value: function renderStatus() {
if (!this.props.showStatus) {
return null;
}
return _react2.default.createElement(
'p',
{ className: 'carousel-status' },
this.props.statusFormatter(this.state.selectedItem + 1, this.props.children.length)
);
}
}, {
key: 'renderThumbs',
value: function renderThumbs() {
if (!this.props.showThumbs || this.props.children.length === 0) {
return null;
}
return _react2.default.createElement(
_Thumbs2.default,
{ onSelectItem: this.handleClickThumb, selectedItem: this.state.selectedItem, transitionTime: this.props.transitionTime },
this.props.children
);
}
}, {
key: 'render',
value: function render() {
if (!this.props.children || this.props.children.length === 0) {
return null;
}
var itemsLength = this.props.children.length;
var isHorizontal = this.props.axis === 'horizontal';
var canShowArrows = this.props.showArrows && itemsLength > 1;
// show left arrow?
var hasPrev = canShowArrows && (this.state.selectedItem > 0 || this.props.infiniteLoop);
// show right arrow
var hasNext = canShowArrows && (this.state.selectedItem < itemsLength - 1 || this.props.infiniteLoop);
// obj to hold the transformations and styles
var itemListStyles = {};
var currentPosition = -this.state.selectedItem * 100 + '%';
// if 3d is available, let's take advantage of the performance of transform
var transformProp = (0, _CSSTranslate2.default)(currentPosition, this.props.axis);
var transitionTime = this.props.transitionTime + 'ms';
itemListStyles = {
'WebkitTransform': transformProp,
'MozTransform': transformProp,
'MsTransform': transformProp,
'OTransform': transformProp,
'transform': transformProp,
'msTransform': transformProp
};
if (!this.state.swiping) {
itemListStyles = _extends({}, itemListStyles, {
'WebkitTransitionDuration': transitionTime,
'MozTransitionDuration': transitionTime,
'MsTransitionDuration': transitionTime,
'OTransitionDuration': transitionTime,
'transitionDuration': transitionTime,
'msTransitionDuration': transitionTime
});
}
var swiperProps = {
selectedItem: this.state.selectedItem,
className: _cssClasses2.default.SLIDER(true, this.state.swiping),
onSwipeMove: this.onSwipeMove,
onSwipeStart: this.onSwipeStart,
onSwipeEnd: this.onSwipeEnd,
style: itemListStyles,
tolerance: this.props.swipeScrollTolerance,
ref: 'itemList'
};
var containerStyles = {};
if (isHorizontal) {
swiperProps.onSwipeLeft = this.increment;
swiperProps.onSwipeRight = this.decrement;
if (this.props.dynamicHeight) {
var itemHeight = this.getVariableImageHeight(this.state.selectedItem);
swiperProps.style.height = itemHeight || 'auto';
containerStyles.height = itemHeight || 'auto';
}
} else {
swiperProps.onSwipeUp = this.decrement;
swiperProps.onSwipeDown = this.increment;
swiperProps.style.height = this.state.itemSize;
containerStyles.height = this.state.itemSize;
}
return _react2.default.createElement(
'div',
{ className: this.props.className, ref: 'carouselWrapper' },
_react2.default.createElement(
'div',
{ className: _cssClasses2.default.CAROUSEL(true), style: { width: this.props.width } },
_react2.default.createElement('button', { type: 'button', className: _cssClasses2.default.ARROW_PREV(!hasPrev), onClick: this.decrement }),
_react2.default.createElement(
'div',
{ className: _cssClasses2.default.WRAPPER(true, this.props.axis), style: containerStyles, ref: 'itemsWrapper' },
_react2.default.createElement(
_reactEasySwipe2.default,
_extends({ tagName: 'ul' }, swiperProps, { allowMouseEvents: this.props.emulateTouch }),
this.renderItems()
)
),
_react2.default.createElement('button', { type: 'button', className: _cssClasses2.default.ARROW_NEXT(!hasNext), onClick: this.increment }),
this.renderControls(),
this.renderStatus()
),
this.renderThumbs()
);
}
}]);
return Carousel;
}(_react.Component);
Carousel.displayName = 'Carousel';
Carousel.propTypes = {
className: _propTypes2.default.string,
children: _propTypes2.default.node,
showArrows: _propTypes2.default.bool,
showStatus: _propTypes2.default.bool,
showIndicators: _propTypes2.default.bool,
infiniteLoop: _propTypes2.default.bool,
showThumbs: _propTypes2.default.bool,
selectedItem: _propTypes2.default.number,
onClickItem: _propTypes2.default.func.isRequired,
onClickThumb: _propTypes2.default.func.isRequired,
onChange: _propTypes2.default.func.isRequired,
axis: _propTypes2.default.oneOf(['horizontal', 'vertical']),
width: customPropTypes.unit,
useKeyboardArrows: _propTypes2.default.bool,
autoPlay: _propTypes2.default.bool,
stopOnHover: _propTypes2.default.bool,
interval: _propTypes2.default.number,
transitionTime: _propTypes2.default.number,
swipeScrollTolerance: _propTypes2.default.oneOfType([_propTypes2.default.number]),
dynamicHeight: _propTypes2.default.bool,
emulateTouch: _propTypes2.default.bool,
statusFormatter: _propTypes2.default.func.isRequired
};
Carousel.defaultProps = {
showIndicators: true,
showArrows: true,
showStatus: true,
showThumbs: true,
infiniteLoop: false,
selectedItem: 0,
axis: 'horizontal',
width: '100%',
useKeyboardArrows: false,
autoPlay: false,
stopOnHover: true,
interval: 3000,
transitionTime: 350,
swipeScrollTolerance: 5,
dynamicHeight: false,
emulateTouch: false,
onClickItem: noop,
onClickThumb: noop,
onChange: noop,
statusFormatter: defaultStatusFormatter
};
exports.default = Carousel;