react-multi-carousel
Version:
Lightweight fully customizable React carousel component supports multiple items and SSR(Server-side rendering) with typescript.
486 lines • 20.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const React = require("react");
const utils_1 = require("./utils");
const defaultTransitionDuration = 300;
const defaultTransition = "transform 300ms ease-in-out";
class Carousel extends React.Component {
constructor(props) {
super(props);
this.containerRef = React.createRef();
this.state = {
itemWidth: 0,
slidesToShow: 0,
currentSlide: 0,
totalItems: React.Children.count(props.children),
deviceType: "",
domLoaded: false,
transform: 0,
containerWidth: 0
};
this.onResize = this.onResize.bind(this);
this.handleDown = this.handleDown.bind(this);
this.handleMove = this.handleMove.bind(this);
this.handleOut = this.handleOut.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.handleEnter = this.handleEnter.bind(this);
this.next = this.next.bind(this);
this.previous = this.previous.bind(this);
this.getIfSlideIsVisbile = this.getIfSlideIsVisbile.bind(this);
this.onMove = false;
this.initialPosition = 0;
this.lastPosition = 0;
this.isAnimationAllowed = true;
this.direction = "";
}
componentDidMount() {
this.setState({ domLoaded: true });
this.setItemsToShow();
window.addEventListener("resize", this.onResize);
this.onResize();
if (this.props.keyBoardControl) {
window.addEventListener("keyup", this.onKeyUp);
}
if (this.props.autoPlay && this.props.autoPlaySpeed) {
this.autoPlay = setInterval(this.next, this.props.autoPlaySpeed);
}
}
setItemsToShow(shouldCorrectItemPosition) {
const { responsive } = this.props;
Object.keys(responsive).forEach(item => {
const { breakpoint, items } = responsive[item];
const { max, min } = breakpoint;
if (window.innerWidth >= min && window.innerWidth <= max) {
this.setState({ slidesToShow: items, deviceType: item });
this.setContainerAndItemWidth(items, shouldCorrectItemPosition);
}
});
}
setContainerAndItemWidth(slidesToShow, shouldCorrectItemPosition) {
if (this.containerRef && this.containerRef.current) {
const containerWidth = this.containerRef.current.offsetWidth;
const itemWidth = Math.round(this.containerRef.current.offsetWidth / slidesToShow);
this.setState({
containerWidth,
itemWidth
});
if (shouldCorrectItemPosition) {
this.correctItemsPosition(itemWidth);
}
}
}
correctItemsPosition(itemWidth) {
if (!this.isAnimationAllowed) {
this.isAnimationAllowed = true;
}
this.setState({
transform: -(itemWidth * this.state.currentSlide)
});
}
onResize() {
this.setItemsToShow();
}
componentDidUpdate({ keyBoardControl, autoPlay }, { containerWidth }) {
if (this.containerRef &&
this.containerRef.current &&
this.containerRef.current.offsetWidth !== containerWidth) {
setTimeout(() => {
this.setItemsToShow(true);
}, this.props.transitionDuration || defaultTransitionDuration);
}
if (keyBoardControl && !this.props.keyBoardControl) {
window.removeEventListener("keyup", this.onKeyUp);
}
if (autoPlay && !this.props.autoPlay && this.autoPlay) {
clearInterval(this.autoPlay);
this.autoPlay = undefined;
}
if (!autoPlay && this.props.autoPlay && !this.autoPlay) {
this.autoPlay = setInterval(this.next, this.props.autoPlaySpeed);
}
}
resetAllItems() {
const { afterChanged, beforeChanged } = this.props;
const previousSlide = this.state.currentSlide;
if (typeof beforeChanged === "function") {
beforeChanged(0, this.getState());
}
this.setState({ transform: 0, currentSlide: 0 }, () => {
if (typeof afterChanged === "function") {
setTimeout(() => {
afterChanged(previousSlide, this.getState());
}, this.props.transitionDuration || defaultTransitionDuration);
}
});
}
next(slidesHavePassed = 0) {
this.isAnimationAllowed = true;
const { slidesToShow } = this.state;
const { slidesToSlide, infinite, afterChanged, beforeChanged } = this.props;
const nextMaximumSlides = this.state.currentSlide +
1 +
slidesHavePassed +
slidesToShow +
slidesToSlide;
const nextSlides = this.state.currentSlide + slidesHavePassed + slidesToSlide;
const nextPosition = -(this.state.itemWidth * nextSlides);
const previousSlide = this.state.currentSlide;
if (nextMaximumSlides <= this.state.totalItems) {
if (typeof beforeChanged === "function") {
beforeChanged(nextSlides, this.getState());
}
this.setState({
transform: nextPosition,
currentSlide: nextSlides
}, () => {
if (typeof afterChanged === "function") {
setTimeout(() => {
afterChanged(previousSlide, this.getState());
}, this.props.transitionDuration || defaultTransitionDuration);
}
});
}
else if (nextMaximumSlides > this.state.totalItems &&
this.state.currentSlide !== this.state.totalItems - slidesToShow) {
// prevent oversliding;
const maxSlides = this.state.totalItems - slidesToShow;
const maxPosition = -(this.state.itemWidth * maxSlides);
if (typeof beforeChanged === "function") {
beforeChanged(maxSlides, this.getState());
}
this.setState({
transform: maxPosition,
currentSlide: maxSlides
}, () => {
if (typeof afterChanged === "function") {
setTimeout(() => {
afterChanged(previousSlide, this.getState());
}, this.props.transitionDuration || defaultTransitionDuration);
}
});
}
else {
if (infinite) {
this.resetAllItems();
}
}
}
previous(slidesHavePassed = 0) {
this.isAnimationAllowed = true;
const { slidesToShow } = this.state;
const { slidesToSlide, infinite, afterChanged, beforeChanged } = this.props;
const nextSlides = this.state.currentSlide - slidesHavePassed - slidesToSlide;
const nextPosition = -(this.state.itemWidth * nextSlides);
const previousSlide = this.state.currentSlide;
if (nextSlides >= 0) {
if (typeof beforeChanged === "function") {
beforeChanged(nextSlides, this.getState());
}
this.setState({
transform: nextPosition,
currentSlide: nextSlides
}, () => {
if (typeof afterChanged === "function") {
setTimeout(() => {
afterChanged(previousSlide, this.getState());
}, this.props.transitionDuration || defaultTransitionDuration);
}
});
}
else if (nextSlides < 0 && this.state.currentSlide !== 0) {
// prevent oversliding.
if (typeof beforeChanged === "function") {
beforeChanged(0, this.getState());
}
this.setState({
transform: 0,
currentSlide: 0
}, () => {
if (typeof afterChanged === "function") {
setTimeout(() => {
afterChanged(previousSlide, this.getState());
}, this.props.transitionDuration || defaultTransitionDuration);
}
});
}
else {
const maxSlides = this.state.totalItems - slidesToShow;
const maxPosition = -(this.state.itemWidth * maxSlides);
if (infinite) {
if (typeof beforeChanged === "function") {
beforeChanged(maxSlides, this.getState());
}
this.setState({
transform: maxPosition,
currentSlide: maxSlides
}, () => {
if (typeof afterChanged === "function") {
setTimeout(() => {
afterChanged(previousSlide, this.getState());
}, this.props.transitionDuration || defaultTransitionDuration);
}
});
}
}
}
componentWillUnmount() {
window.removeEventListener("resize", this.onResize);
if (this.props.keyBoardControl) {
window.removeEventListener("keyup", this.onKeyUp);
}
if (this.props.autoPlay && this.autoPlay) {
clearInterval(this.autoPlay);
this.autoPlay = undefined;
}
}
resetMoveStatus() {
this.onMove = false;
this.initialPosition = 0;
this.lastPosition = 0;
this.direction = "";
}
handleDown(e) {
if ((e.touches && this.props.disableSwipeOnMobile) ||
(e && !e.touches && this.props.disableDrag)) {
return;
}
const { clientX } = e.touches ? e.touches[0] : e;
this.onMove = true;
this.initialPosition = clientX;
this.lastPosition = clientX;
this.isAnimationAllowed = false;
}
handleMove(e) {
if ((e.touches && this.props.disableSwipeOnMobile) ||
(e && !e.touches && this.props.disableDrag)) {
return;
}
const { clientX } = e.touches ? e.touches[0] : e;
if (e.touches && this.autoPlay && this.props.autoPlay) {
clearInterval(this.autoPlay);
this.autoPlay = undefined;
}
if (this.onMove) {
if (this.initialPosition > clientX) {
this.direction = "right";
const translateXLimit = Math.abs(-(this.state.itemWidth *
(this.state.totalItems - this.state.slidesToShow)));
const nextTranslate = this.state.transform - (this.lastPosition - clientX);
const isLastSlide = this.state.currentSlide ===
this.state.totalItems - this.state.slidesToShow;
if (Math.abs(nextTranslate) <= translateXLimit ||
(isLastSlide && this.props.infinite)) {
this.setState({ transform: nextTranslate });
}
}
if (clientX > this.initialPosition) {
this.direction = "left";
const nextTranslate = this.state.transform + (clientX - this.lastPosition);
const isFirstSlide = this.state.currentSlide === 0;
if (nextTranslate <= 0 || (isFirstSlide && this.props.infinite)) {
this.setState({ transform: nextTranslate });
}
}
this.lastPosition = clientX;
}
}
handleOut(e) {
if (this.props.autoPlay && !this.autoPlay) {
this.autoPlay = setInterval(this.next, this.props.autoPlaySpeed);
}
const shouldDisableOnMobile = e.type === "touchend" && this.props.disableSwipeOnMobile;
const shouldDisableOnDesktop = (e.type === "mouseleave" || e.type === "mouseup") &&
this.props.disableDrag;
if (shouldDisableOnMobile || shouldDisableOnDesktop) {
return;
}
if (this.onMove) {
if (this.direction === "right") {
const slidesHavePassed = Math.round((this.initialPosition - this.lastPosition) / this.state.itemWidth);
if (this.initialPosition - this.lastPosition >=
this.props.minimumTouchDrag) {
this.next(slidesHavePassed);
}
else {
this.correctItemsPosition(this.state.itemWidth);
}
}
if (this.direction === "left") {
const slidesHavePassed = Math.round((this.lastPosition - this.initialPosition) / this.state.itemWidth);
if (this.lastPosition - this.initialPosition >
this.props.minimumTouchDrag) {
this.previous(slidesHavePassed);
}
else {
this.correctItemsPosition(this.state.itemWidth);
}
}
this.resetMoveStatus();
}
}
onKeyUp(e) {
switch (e.keyCode) {
case 37:
return this.previous();
case 39:
return this.next();
}
}
handleEnter() {
if (this.autoPlay && this.props.autoPlay) {
clearInterval(this.autoPlay);
this.autoPlay = undefined;
}
}
goToSlide(slide) {
const { itemWidth } = this.state;
const { afterChanged, beforeChanged } = this.props;
const previousSlide = this.state.currentSlide;
if (typeof beforeChanged === "function") {
beforeChanged(slide, this.getState());
}
this.setState({
currentSlide: slide,
transform: -(itemWidth * slide)
}, () => {
if (typeof afterChanged === "function") {
setTimeout(() => {
afterChanged(previousSlide, this.getState());
}, this.props.transitionDuration || defaultTransitionDuration);
}
});
}
getState() {
return Object.assign({}, this.state, { onMove: this.onMove, direction: this.direction });
}
renderLeftArrow() {
const { customLeftArrow } = this.props;
if (customLeftArrow) {
return React.cloneElement(customLeftArrow, {
onClick: () => this.previous(),
carouselState: this.getState()
});
}
else {
return (React.createElement("button", { className: "react-multiple-carousel__arrow react-multiple-carousel__arrow--left", onClick: () => this.previous() }));
}
}
renderRightArrow() {
const { customRightArrow } = this.props;
if (customRightArrow) {
return React.cloneElement(customRightArrow, {
onClick: () => this.next(),
carouselState: this.getState()
});
}
else {
return (React.createElement("button", { className: "react-multiple-carousel__arrow react-multiple-carousel__arrow--right", onClick: () => this.next() }));
}
}
renderButtonGroups() {
const { customButtonGroup } = this.props;
if (customButtonGroup) {
return React.cloneElement(customButtonGroup, {
previous: () => this.previous(),
next: () => this.next(),
goToSlide: (slideIndex) => this.goToSlide(slideIndex),
carouselState: this.getState()
});
}
return null;
}
renderDotsList() {
const { customDot, dotListClassName } = this.props;
return (React.createElement("ul", { className: `react-multi-carousel-dot-list ${dotListClassName}` }, Array(this.state.totalItems)
.fill(0)
.map((item, index) => {
if (customDot) {
return React.cloneElement(customDot, {
index,
onClick: () => this.goToSlide(index),
carouselState: this.getState()
});
}
return (React.createElement("li", { key: index, className: `react-multi-carousel-dot ${this.state.currentSlide === index
? "react-multi-carousel-dot--active"
: ""}` },
React.createElement("button", { onClick: () => this.goToSlide(index) })));
})));
}
getIfSlideIsVisbile(index) {
return (index >= this.state.currentSlide &&
index < this.state.currentSlide + this.state.slidesToShow);
}
render() {
const { domLoaded, slidesToShow, containerWidth, itemWidth } = this.state;
const { deviceType, responsive, forSSR, children, slidesToSlide, removeArrow, removeArrowOnDeviceType, infinite, containerClassName, contentClassName, itemClassName, customTransition, partialVisbile } = this.props;
let flexBisis;
const domFullyLoaded = domLoaded && slidesToShow && containerWidth && itemWidth;
if (forSSR && deviceType && !domFullyLoaded) {
flexBisis = utils_1.guessWidthFromDeviceType(deviceType, responsive);
}
const shouldRenderOnSSR = forSSR && deviceType && !domFullyLoaded && flexBisis;
const isLeftEndReach = !(this.state.currentSlide - slidesToSlide >= 0);
const isRightEndReach = !(this.state.currentSlide + 1 + slidesToShow <=
this.state.totalItems);
const shouldShowArrows = !removeArrow &&
!(removeArrowOnDeviceType &&
((deviceType && removeArrowOnDeviceType.indexOf(deviceType) > -1) ||
(this.state.deviceType &&
removeArrowOnDeviceType.indexOf(this.state.deviceType) > -1)));
const disableLeftArrow = !infinite && isLeftEndReach;
const disableRightArrow = !infinite && isRightEndReach;
const paritialVisibilityGutter = utils_1.getParitialVisibilityGutter(responsive, partialVisbile, deviceType, this.state.deviceType);
// this is the perfect formular, the perfect code.
const currentTransform = paritialVisibilityGutter && partialVisbile
? partialVisbile === "right"
? this.state.transform +
this.state.currentSlide * paritialVisibilityGutter
: this.state.transform +
this.state.currentSlide * paritialVisibilityGutter +
(this.state.currentSlide === 0
? 0
: paritialVisibilityGutter + paritialVisibilityGutter / 2)
: this.state.transform;
return (React.createElement("div", { className: `react-multi-carousel-list ${containerClassName}`, ref: this.containerRef },
React.createElement("ul", { className: `react-multi-carousel-track ${contentClassName}`,
// @ts-ignore
style: {
transition: this.isAnimationAllowed
? customTransition || defaultTransition
: "none",
overflow: shouldRenderOnSSR ? "hidden" : "unset",
transform: `translate3d(${currentTransform}px,0,0)`
}, onMouseMove: this.handleMove, onMouseDown: this.handleDown, onMouseUp: this.handleOut, onMouseEnter: this.handleEnter, onMouseLeave: this.handleOut, onTouchStart: this.handleDown, onTouchMove: this.handleMove, onTouchEnd: this.handleOut }, React.Children.toArray(children).map((child, index) => (React.createElement("li", { key: index, style: {
flex: shouldRenderOnSSR ? `1 0 ${flexBisis}%` : "auto",
position: "relative",
width: domFullyLoaded
? `${partialVisbile && paritialVisibilityGutter
? itemWidth - paritialVisibilityGutter
: itemWidth}px`
: "auto"
}, className: itemClassName }, React.cloneElement(child, {
index,
isvisible: this.getIfSlideIsVisbile(index),
carouselState: this.getState()
}))))),
shouldShowArrows && !disableLeftArrow && this.renderLeftArrow(),
shouldShowArrows && !disableRightArrow && this.renderRightArrow(),
this.renderButtonGroups(),
this.props.shouldShowDots && this.renderDotsList()));
}
}
Carousel.defaultProps = {
slidesToSlide: 1,
infinite: false,
containerClassName: "",
contentClassName: "",
itemClassName: "",
keyBoardControl: true,
autoPlaySpeed: 3000,
shouldShowDots: false,
minimumTouchDrag: 50,
dotListClassName: ""
};
exports.default = Carousel;
//# sourceMappingURL=Carousel.js.map