Hi everyone!
This is part 4 of my Simple React Carousel series, it will also be the last part of this series, next I will try to release a package based on this series!
In this last part, I will talk about how to handle infinite loop in Carousel, which mean we can go back to the first item when we click next from the first item.
Prequisites
Check my previous part on this series to see how to create a working Simple React Carousel, or you can also clone the project from previous part from my Github repo.
Handle infinite loop
To tell the Carousel component that we want to show an infinite loop, we will need to pass a new props to it. The props name will be infiniteLoop
.
Next we need to clone the last item to be placed before the first item and clone the first item to be placed after the last item. We need to do this so that when the Carousel reach the end, it will still be able to render the first item and after that we move the carousel to the actual first item.
To move the carousel to the actual first or last item, we need to add onTransitionEnd
property to our div
with carousel-content class. In the onTransitionEnd, we will check disable the animation so that when we move the Carousel to the actual location there is no animation so to the user eyes it doesn't look any different. After disabling the animation, we need to change the currentIndex
of the Carousel. After the currentIndex is modified, then we enable the animation again.
index.js
// ...
<div
show={3}
+ infiniteLoop={true}
>
// ...
And here is the completed Carousel.js file.
Carousel.js
const Carousel = (props) => {
const {children, show, infiniteLoop} = props
const [currentIndex, setCurrentIndex] = useState(infiniteLoop ? show : 0)
const [length, setLength] = useState(children.length)
const [isRepeating, setIsRepeating] = useState(infiniteLoop && children.length > show)
const [transitionEnabled, setTransitionEnabled] = useState(true)
const [touchPosition, setTouchPosition] = useState(null)
// Set the length to match current children from props
useEffect(() => {
setLength(children.length)
setIsRepeating(infiniteLoop && children.length > show)
}, [children, infiniteLoop, show])
useEffect(() => {
if (isRepeating) {
if (currentIndex === show || currentIndex === length) {
setTransitionEnabled(true)
}
}
}, [currentIndex, isRepeating, show, length])
const next = () => {
if (isRepeating || currentIndex < (length - show)) {
setCurrentIndex(prevState => prevState + 1)
}
}
const prev = () => {
if (isRepeating || currentIndex > 0) {
setCurrentIndex(prevState => prevState - 1)
}
}
const handleTouchStart = (e) => {
const touchDown = e.touches[0].clientX
setTouchPosition(touchDown)
}
const handleTouchMove = (e) => {
const touchDown = touchPosition
if(touchDown === null) {
return
}
const currentTouch = e.touches[0].clientX
const diff = touchDown - currentTouch
if (diff > 5) {
next()
}
if (diff < -5) {
prev()
}
setTouchPosition(null)
}
const handleTransitionEnd = () => {
if (isRepeating) {
if (currentIndex === 0) {
setTransitionEnabled(false)
setCurrentIndex(length)
} else if (currentIndex === length + show) {
setTransitionEnabled(false)
setCurrentIndex(show)
}
}
}
const renderExtraPrev = () => {
let output = []
for (let index = 0; index < show; index++) {
output.push(children[length - 1 - index])
}
output.reverse()
return output
}
const renderExtraNext = () => {
let output = []
for (let index = 0; index < show; index++) {
output.push(children[index])
}
return output
}
return (
<div className="carousel-container">
<div className="carousel-wrapper">
{/* You can alwas change the content of the button to other things */}
{
(isRepeating || currentIndex > 0) &&
<button onClick={prev} className="left-arrow">
<
</button>
}
<div
className="carousel-content-wrapper"
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
>
<div
className={`carousel-content show-${show}`}
style={{
transform: `translateX(-${currentIndex * (100 / show)}%)`,
transition: !transitionEnabled ? 'none' : undefined,
}}
onTransitionEnd={() => handleTransitionEnd()}
>
{
(length > show && isRepeating) &&
renderExtraPrev()
}
{children}
{
(length > show && isRepeating) &&
renderExtraNext()
}
</div>
</div>
{/* You can alwas change the content of the button to other things */}
{
(isRepeating || currentIndex < (length - show)) &&
<button onClick={next} className="right-arrow">
>
</button>
}
</div>
</div>
)
}
And that's it! You can check the finished project in my Github.
This is the end of my React Carousel series, I will try to publish a package based on this series in the future.
Follow me to get the latest info!
Top comments (6)
You are duping other people, this is not infinite carousel with multiple items, you just appended the same elements before and after.
yes, but I do not know a better way. Do you know?
Not working well on iOS devices
Great post. But to anyone that reads your posts, I'll add that his github repo is even better! He has way more updates on it so definitely check it out. Thanks again!
Thank you for this lovely tutorial. Would you please tell me how to make it autoplay? Thank you again.
Thank you for this well explained tutorial. However I', struggling with the tests for touch events, and found no success. Did you manage to write tests for this?