Are you looking to create a swipeable carousel with responsive navigation and changing colors in React/NEXT? Look no further! In this tutorial, we will walk you through the steps to create a carousel that you can swipe through, with navigation that changes color based on the current swipe.
In this example, we have an array of useRef hooks called refsArray. We then attach a scroll event listener to the container element with using the useEffect hook. Inside the event listener, we get the current horizontal scroll position of the container element using its scrollLeft property.
Depending on the horizontal scroll position, we update the current reference of the corresponding useRef hook in the refsArray. Finally, we render the content of the component and pass each useRef hook to the corresponding section using the ref prop. The container element is given a style with overflowX: 'scroll' to create the horizontal overflow scroll. The child elements are set to display as a flex container to ensure that they are aligned horizontally and can be scrolled together.
Prerequisites
Before we dive in, you will need to have some basic knowledge of React and JavaScript. You should also have Node.js and npm installed on your machine.
To build our swipeable carousel, we will be using React's built-in useRef and useState hooks, as well as the scrollIntoView method.
Creating the Refs
Next, we need to create refs for each item in our array. We can do this using the useRef hook:
const myRefs = useRef([]);
myRefs.current = array.map((item, index) => myRefs.current[index] ?? createRef());
This code creates an array of refs using the map method and sets it to myRefs.current.
Setting the Initial Swipe
We need to keep track of the current swipe in our carousel, so let's use the useState hook to set the initial swipe to the first item in our array:
const [currentSwipe, setCurrentSwipe] = useState(array[0]);
Creating the Navigation
Now, let's create the navigation for our carousel. We will use the map method to loop through our array of items, and create a navigation item for each one:
<nav className="navigation">
{array.map((item, idx) => (
<div
onClick={() => takeTo(myRefs.current[array?.indexOf(item)])}
style={{ backgroundColor: `${currentSwipe === item ? "red" : ""}` }}
>
{item}
</div>
))}
</nav>
//style
.navigation{
display: flex;
justify-content: space-around;
margin: 4px;
font-size: 25px;
cursor: pointer;
}
In this code, we create a div element for each item in our array, with an onClick function that takes us to the corresponding item in the carousel using the takeTo function. We also set the background color of the current navigation item to red using the currentSwipe state.
Creating the Carousel
Finally, let's create the carousel itself. We will use the map method again to loop through our array of items, and create a div element for each one :
The carousel itself will have the "overflow-x" set to "hidden", and the "swipe_option" will have "min-width" set to "95%"
<section id="container" onScroll={handleScrollRef} className="swipe">
{array.map((item, idx) => (
<div ref={myRefs.current[idx]} className="swipe_option">
{item}
</div>
))}
</section>
//style
.swipe{
display: flex;
flex-direction: row;
overflow-x: scroll;
gap: 5px;
}
.swipe_option {
min-width: calc(95%);
display: flex;
justify-content: center;
align-items: center;
scroll-snap-align: start;
cursor: pointer;
height: 500px;
background-color: black;
font-size: 50px;
color: white
}
On scroll of the section containing swipes we call handleScrollRef.
takeTo()
takeTo is a function that scrolls the view to a particular element in the carousel. This function takes a single argument, which is a React ref that represents the element to which the view should be scrolled.
In the onClick handler for each navigation item, takeTo is called with the corresponding ref for the item. The scrollIntoView method is then called on the ref's current value to smoothly scroll the view to the desired element.
function takeTo(ref) {
ref.current.scrollIntoView({
behavior: "smooth",
block: "nearest",
});
}
Handle Scroll Ref
handleScrollRef is a function that handles the scrolling of the carousel and updates the currentSwipe state accordingly. This function is attached to the onScroll event of the carousel container.
const handleScrollRef = () => {
const container = document.querySelector("#container");
const scrollPosition = container?.scrollLeft;
const containerWidth = container?.clientWidth;
setCurrentSwipe(array[Math.round(scrollPosition / containerWidth)]);
};
First, the function retrieves a reference to the carousel container element by querying the DOM using the querySelector method. It uses the id selector to select the #container element.
Then, the function calculates the current scroll position of the container using the scrollLeft property. This property returns the number of pixels the container has been scrolled horizontally.
Next, the function calculates the width of the container using the clientWidth property. This property returns the inner width of the container, excluding the horizontal scrollbar.
Finally, the function uses these values to calculate the index of the current swipe, based on the position of the horizontal scrollbar. It does this by dividing the scroll position by the container width and rounding to the nearest integer. It then uses this index to update the currentSwipe state.
Overall, the handleScrollRef function is a simple and effective way to synchronize the navigation with the current position of the carousel based on user scrolling.
Complete code
import React from "react";
import { createRef, useRef, useState } from "react";
import "./styles.css";
export default function App() {
const array = ["Swipe 1", "Swipe 2", "Swipe 3"];
//reference creation
const myRefs = useRef([]);
myRefs.current = array.map((item, index) => myRefs.current[index] ?? createRef());
//take to reference
function takeTo(ref) {
ref.current.scrollIntoView({
behavior: "smooth",
block: "nearest",
});
}
//initial swipe is first element of array
const [currentSwipe, setCurrentSwipe] = useState(array[0]);
const handleScrollRef = () => {
const container = document.querySelector("#container");
const scrollPosition = container?.scrollLeft;
const containerWidth = container?.clientWidth;
setCurrentSwipe(array[Math.round(scrollPosition / containerWidth)]);
};
return (
<main>
<nav className="navigation" >
{array.map((item, idx) =>
<div
onClick={() => takeTo(myRefs.current[array?.indexOf(item)])}
style={{backgroundColor: `${currentSwipe === item ? "green" : "" }`}}
>
{item}
</div>
)}
</nav>
<section id="container" onScroll={handleScrollRef} className="swipe" >
{array.map((item, idx) =>
<div
ref={myRefs.current[idx]}
className="swipe_option"
>
{item}
</div>
)}
</section>
</main>
);
}
CSS file
.navigation{
display: flex;
justify-content: space-around;
margin: 4px;
font-size: 25px;
cursor: pointer;
}
.swipe{
display: flex;
flex-direction: row;
overflow-x: scroll;
gap: 5px;
}
.swipe_option {
min-width: calc(95%);
display: flex;
justify-content: center;
align-items: center;
scroll-snap-align: start;
cursor: pointer;
height: 500px;
background-color: black;
font-size: 50px;
color: white
}