component-slider
Version:
used to show react components in carousal format
261 lines (244 loc) • 8 kB
JavaScript
import React, { PropTypes, Component } from 'react';
import Slide from './Slide';
import classNames from 'classnames';
class Slider extends Component {
/**
*
* @param props
*/
constructor(props) {
super(props);
const { circularItems, selectedIndex } = this.props;
const sliderLength = circularItems.length;
// On slide we will first load empty object in slider
// then load images in them
this.itemsToRender = (new Array(sliderLength)).fill(null);
this.translateLeft = 0;
this.leftOffset = 0;
if (props.isCircular) {
this.leftOffset++;
}
this.updateItems(selectedIndex);
}
componentDidMount() {
if (this.props.autoplay) {
this.interval = setInterval(() => { this.onClick(false); }, 10000);
}
}
state = {
leftIndex: this.props.selectedIndex,
isAnimation: false
};
// This handles change in isVisible that comes from the parent. will not be called for others
// because two separate arrays are maintained and the one showing is not coming from props.
componentWillReceiveProps(nextProps) {
const { selectedIndex } = nextProps;
let { leftIndex } = this.state;
if (selectedIndex !== leftIndex) {
leftIndex = selectedIndex + 1;
this.setState({ leftIndex: selectedIndex });
}
this.updateItems(leftIndex, nextProps);
}
componentDidUpdate() {
const { onChangeHandler } = this.props;
const { isAnimation, leftIndex } = this.state;
onChangeHandler(leftIndex);
if (isAnimation) {
this.sliderTrack.addEventListener('transitionend',
this.onTransitionEnd,
false);
}
}
onClick(isLeft) {
if (!this.state.isAnimation) {
let { circularItems, noOfSlidesShown } = this.props;
const sliderLength = circularItems.length;
const sliderItemWidth = 100 / sliderLength;
// multiplier -1 means element is sliding to left
let multiplier = -1;
if (isLeft) {
multiplier = 1;
}
if (!this.props.isCircular) {
noOfSlidesShown = this.getNumberShown(isLeft, this.state.leftIndex);
}
this.translateLeft = multiplier * noOfSlidesShown * sliderItemWidth;
this.updateItems(this.state.leftIndex - multiplier);
this.setState({
isAnimation: true,
isLeft
});
}
}
onTransitionEnd = () => {
let { leftIndex } = this.state;
const { circularItems, noOfSlidesShown, onChangeHandler } = this.props;
const sliderLength = circularItems.length;
if (this.state.isLeft) {
leftIndex -= 1;
if (leftIndex <= (-1 + this.leftOffset)) {
leftIndex = (sliderLength / noOfSlidesShown) - 1 - this.leftOffset;
}
} else {
leftIndex += 1;
if ((leftIndex + this.leftOffset) * noOfSlidesShown >= sliderLength) {
leftIndex = this.leftOffset;
}
}
onChangeHandler((leftIndex - this.leftOffset) * noOfSlidesShown);
this.sliderTrack.removeEventListener('transitionend', this.onTransitionEnd);
this.updateItems(leftIndex);
this.setState({
isAnimation: false,
leftIndex
});
}
// function to get the number of items to be shifted
getNumberShown(isLeft, index) {
const { circularItems, noOfSlidesShown } = this.props;
let idx = index;
const sliderLength = circularItems.length;
if ((idx === sliderLength / noOfSlidesShown - 2 - this.leftOffset && !isLeft) ||
(idx === sliderLength / noOfSlidesShown - 1 - this.leftOffset && isLeft)
) {
if (!isLeft) {
idx += 1;
}
// get the count of valid elements i.e. not undefined/null
let count = 0;
for (let startIndex = idx * noOfSlidesShown;
startIndex < (idx + 1) * noOfSlidesShown;
startIndex++
) {
if (circularItems[startIndex]) {
count++;
}
}
return count;
}
return noOfSlidesShown;
}
// function to get the left pos of the carousel
getLeft() {
const { isLeft, isAnimation } = this.state;
const { leftIndex } = this.state;
const { noOfSlidesShown } = this.props;
if (!this.props.isCircular && !this.props.sliderWrap) {
let slidesShown = noOfSlidesShown;
// we have to shift only when move right and animation is false
// or move left and animation is true
if (!isLeft && !isAnimation) {
slidesShown = this.getNumberShown(false, leftIndex - 1);
} else if (isLeft && isAnimation) {
slidesShown = this.getNumberShown(isLeft, leftIndex);
}
// this shift will depend on clides to be shifted
return ((leftIndex - 1) + (slidesShown / noOfSlidesShown)) * -100;
}
// default shift
return leftIndex * -100;
}
// This will update itemsToRender according to the index of the frame which needs to be shown
updateItems(index, props) {
const { noOfSlidesShown } = this.props;
let { circularItems } = this.props;
if (props) {
circularItems = props.circularItems;
}
const indexOffset = noOfSlidesShown - this.getNumberShown(false, index - 1);
for (let startIndex = index * noOfSlidesShown - indexOffset;
startIndex < (index + 1) * noOfSlidesShown - indexOffset;
startIndex++
) {
this.itemsToRender[startIndex] = circularItems[startIndex];
}
}
renderButton = ({ isLeft, className, classNames: s }) => {
return (
<div
className={`${className} ${s.icon}`}
onClick={() => { this.onClick(isLeft); }}
>
<span />
</div>
);
}
componentWillUnmount() {
if (this.props.autoplay) {
clearInterval(this.interval);
}
}
render() {
const { circularItems, noOfSlidesShown, classNames: s, isCircular } = this.props;
const sliderLength = circularItems.length;
const leftVal = this.getLeft();
const sliderTrackWidth = `${(sliderLength / noOfSlidesShown) * 100}%`;
const { isAnimation, leftIndex } = this.state;
const sliderActualLength = (sliderLength / noOfSlidesShown) - 1;
let sliderTrackStyle = {
width: sliderTrackWidth,
left: `${leftIndex * -100}%`
};
if (isAnimation) {
sliderTrackStyle = {
...sliderTrackStyle,
transform: `translate3d(${this.translateLeft}%,0px,0px)`,
transition: 'transform 0.7s ease-in-out'
};
}
return (
<div className={s.slider}>
{
(isCircular || this.state.leftIndex < sliderActualLength) &&
this.renderButton({
isLeft: false,
className: classNames(s['slider-next'], s['slider-next-pos']),
classNames: s
})}
{
(isCircular || this.state.leftIndex > 0) &&
this.renderButton({
isLeft: true,
className: classNames(s['slider-prev'], s['slider-prev-pos']),
classNames: s
})}
<div className={s['slider-list']}>
<div
className={s['slider-track']}
style={sliderTrackStyle}
ref={c => { this.sliderTrack = c; }}
>
{
this.itemsToRender.map((component, i) => (isCircular || component) && <Slide
classNames={s} key={`image-${i}`}
sliderLength={sliderLength}
>
{component}
</Slide>
)
}
</div>
</div>
</div>
);
}
}
Slider.propTypes = {
circularItems: PropTypes.array,
noOfSlidesShown: PropTypes.number,
isCircular: PropTypes.bool,
autoplay: PropTypes.bool,
classNames: PropTypes.object,
selectedIndex: PropTypes.number,
onChangeHandler: PropTypes.func
};
Slider.defaultProps = {
circularItems: [],
noOfSlidesShown: 3,
isCircular: false,
autoplay: false,
selectedIndex: 1,
onChangeHandler: () => {}
};
export default Slider;